From 70e8586f8cbe900b1f258fabe205c4836ae95f64 Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Wed, 8 Mar 2023 23:39:42 +0100 Subject: [PATCH 1/2] feat: implement menu bar --- Cargo.toml | 1 + Justfile | 14 + openapi.json | 836 ++++++++++++++++++++ openapitools.json | 7 + src/api.rs | 8 +- src/bin/openapi.rs | 8 + src/lib.rs | 2 + src/server.rs | 6 +- ui/menu/tsconfig.json => tsconfig.json | 7 +- ui/menu/.eslintrc.js | 78 +- ui/menu/package-lock.json | 42 +- ui/menu/package.json | 10 +- ui/menu/public/index.html | 475 +++++------ ui/menu/rollup.config.js | 262 +++--- ui/menu/src/App.svelte | 43 +- ui/menu/src/components/ImageIcon.svelte | 28 +- ui/menu/src/components/InfoModal.svelte | 188 ++--- ui/menu/src/components/Menu.svelte | 213 ++--- ui/menu/src/components/MenuItemPage.svelte | 28 +- ui/menu/src/components/PageIcon.svelte | 12 +- ui/menu/src/index.ts | 6 +- ui/menu/src/util/api.ts | 18 + ui/menu/src/util/functions.ts | 37 +- ui/menu/src/util/talonData.ts | 73 +- ui/menu/src/util/types.ts | 57 +- ui/menu/testdata/test.json | 90 --- ui/menu/testdata/testSchema.json | 99 --- ui/talon-client/.gitignore | 4 + ui/talon-client/.npmignore | 1 + ui/talon-client/.openapi-generator-ignore | 23 + ui/talon-client/.openapi-generator/FILES | 23 + ui/talon-client/.openapi-generator/VERSION | 1 + ui/talon-client/README.md | 45 ++ ui/talon-client/package-lock.json | 28 + ui/talon-client/package.json | 19 + ui/talon-client/src/apis/DefaultApi.ts | 559 +++++++++++++ ui/talon-client/src/apis/index.ts | 3 + ui/talon-client/src/index.ts | 5 + ui/talon-client/src/models/Info.ts | 96 +++ ui/talon-client/src/models/InfoStats.ts | 83 ++ ui/talon-client/src/models/InfoVersion.ts | 110 +++ ui/talon-client/src/models/SourceIcon.ts | 41 + ui/talon-client/src/models/Stats.ts | 83 ++ ui/talon-client/src/models/Version.ts | 86 ++ ui/talon-client/src/models/VersionFile.ts | 82 ++ ui/talon-client/src/models/VersionInfo.ts | 110 +++ ui/talon-client/src/models/Visibility.ts | 38 + ui/talon-client/src/models/Website.ts | 139 ++++ ui/talon-client/src/models/WebsiteNew.ts | 110 +++ ui/talon-client/src/models/WebsiteUpdate.ts | 111 +++ ui/talon-client/src/models/index.ts | 14 + ui/talon-client/src/runtime.ts | 407 ++++++++++ ui/talon-client/tsconfig.json | 20 + 53 files changed, 3857 insertions(+), 1032 deletions(-) create mode 100644 Justfile create mode 100644 openapi.json create mode 100644 openapitools.json create mode 100644 src/bin/openapi.rs rename ui/menu/tsconfig.json => tsconfig.json (89%) create mode 100644 ui/menu/src/util/api.ts delete mode 100644 ui/menu/testdata/test.json delete mode 100644 ui/menu/testdata/testSchema.json create mode 100644 ui/talon-client/.gitignore create mode 100644 ui/talon-client/.npmignore create mode 100644 ui/talon-client/.openapi-generator-ignore create mode 100644 ui/talon-client/.openapi-generator/FILES create mode 100644 ui/talon-client/.openapi-generator/VERSION create mode 100644 ui/talon-client/README.md create mode 100644 ui/talon-client/package-lock.json create mode 100644 ui/talon-client/package.json create mode 100644 ui/talon-client/src/apis/DefaultApi.ts create mode 100644 ui/talon-client/src/apis/index.ts create mode 100644 ui/talon-client/src/index.ts create mode 100644 ui/talon-client/src/models/Info.ts create mode 100644 ui/talon-client/src/models/InfoStats.ts create mode 100644 ui/talon-client/src/models/InfoVersion.ts create mode 100644 ui/talon-client/src/models/SourceIcon.ts create mode 100644 ui/talon-client/src/models/Stats.ts create mode 100644 ui/talon-client/src/models/Version.ts create mode 100644 ui/talon-client/src/models/VersionFile.ts create mode 100644 ui/talon-client/src/models/VersionInfo.ts create mode 100644 ui/talon-client/src/models/Visibility.ts create mode 100644 ui/talon-client/src/models/Website.ts create mode 100644 ui/talon-client/src/models/WebsiteNew.ts create mode 100644 ui/talon-client/src/models/WebsiteUpdate.ts create mode 100644 ui/talon-client/src/models/index.ts create mode 100644 ui/talon-client/src/runtime.ts create mode 100644 ui/talon-client/tsconfig.json diff --git a/Cargo.toml b/Cargo.toml index 9b19d31..8dd984c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ license = "MIT" description = "Static site management system" build = "build.rs" +default-run = "talon" [dependencies] poem = "1.3.55" diff --git a/Justfile b/Justfile new file mode 100644 index 0000000..d401299 --- /dev/null +++ b/Justfile @@ -0,0 +1,14 @@ +ok: lint test + +lint: + cargo fmt --all + cargo clippy --all --all-features -- -D warnings + +test: + cargo test --workspace + +oai-doc: + cargo run --bin openapi + +oai-client: + openapi-generator-cli generate -i openapi.json -g typescript-fetch -o ui/talon-client -p "npmName=talon-client" diff --git a/openapi.json b/openapi.json new file mode 100644 index 0000000..bef500b --- /dev/null +++ b/openapi.json @@ -0,0 +1,836 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Talon", + "description": "API for the Talon static site management system", + "version": "0.1.0", + "license": { + "name": "MIT License" + } + }, + "servers": [], + "tags": [], + "paths": { + "/website/{subdomain}": { + "get": { + "summary": "Get a website", + "parameters": [ + { + "name": "subdomain", + "schema": { + "type": "string" + }, + "in": "path", + "required": true, + "deprecated": false, + "explode": true + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json; charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Website" + } + } + } + } + } + }, + "put": { + "summary": "Create a new website", + "parameters": [ + { + "name": "subdomain", + "schema": { + "type": "string" + }, + "in": "path", + "required": true, + "deprecated": false, + "explode": true + } + ], + "requestBody": { + "content": { + "application/json; charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/WebsiteNew" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "" + } + }, + "security": [ + { + "ApiKeyAuthorization": [] + } + ] + }, + "patch": { + "summary": "Update website", + "parameters": [ + { + "name": "subdomain", + "schema": { + "type": "string" + }, + "in": "path", + "required": true, + "deprecated": false, + "explode": true + } + ], + "requestBody": { + "content": { + "application/json; charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/WebsiteUpdate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "" + } + }, + "security": [ + { + "ApiKeyAuthorization": [] + } + ] + }, + "delete": { + "summary": "Delete website", + "parameters": [ + { + "name": "subdomain", + "schema": { + "type": "string" + }, + "in": "path", + "required": true, + "deprecated": false, + "explode": true + } + ], + "responses": { + "200": { + "description": "" + } + }, + "security": [ + { + "ApiKeyAuthorization": [] + } + ] + } + }, + "/websites": { + "get": { + "summary": "Get all publicly listed websites", + "description": "Returns all publicly listed websites (visibility != `hidden`)", + "parameters": [ + { + "name": "visibility", + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/Visibility" + }, + { + "default": "hidden" + } + ] + }, + "in": "query", + "description": "Mimimum visibility of the websites", + "required": false, + "deprecated": false, + "explode": true + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json; charset=utf-8": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Website" + } + } + } + } + } + } + } + }, + "/websitesAll": { + "get": { + "summary": "Get all websites", + "description": "Returns all websites from Talon's database (including hidden ones, if the current user\nhas access to them). This endpoint requires authentication (use the `/websites` endpoint\nfor unauthenticated users).", + "parameters": [ + { + "name": "visibility", + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/Visibility" + }, + { + "default": "hidden" + } + ] + }, + "in": "query", + "description": "Mimimum visibility of the websites", + "required": false, + "deprecated": false, + "explode": true + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json; charset=utf-8": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Website" + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuthorization": [] + } + ] + } + }, + "/website/{subdomain}/versions": { + "get": { + "summary": "Get website versions", + "parameters": [ + { + "name": "subdomain", + "schema": { + "type": "string" + }, + "in": "path", + "required": true, + "deprecated": false, + "explode": true + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json; charset=utf-8": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Version" + } + } + } + } + } + } + } + }, + "/website/{subdomain}/version/{version}": { + "get": { + "summary": "Get version", + "parameters": [ + { + "name": "subdomain", + "schema": { + "type": "string" + }, + "in": "path", + "required": true, + "deprecated": false, + "explode": true + }, + { + "name": "version", + "schema": { + "type": "integer", + "format": "uint32" + }, + "in": "path", + "required": true, + "deprecated": false, + "explode": true + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json; charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Version" + } + } + } + } + } + }, + "delete": { + "summary": "Delete version", + "parameters": [ + { + "name": "subdomain", + "schema": { + "type": "string" + }, + "in": "path", + "required": true, + "deprecated": false, + "explode": true + }, + { + "name": "version", + "schema": { + "type": "integer", + "format": "uint32" + }, + "in": "path", + "required": true, + "deprecated": false, + "explode": true + } + ], + "responses": { + "200": { + "description": "" + } + }, + "security": [ + { + "ApiKeyAuthorization": [] + } + ] + } + }, + "/website/{subdomain}/version/{version}/files": { + "get": { + "summary": "Get version files", + "parameters": [ + { + "name": "subdomain", + "schema": { + "type": "string" + }, + "in": "path", + "required": true, + "deprecated": false, + "explode": true + }, + { + "name": "version", + "schema": { + "type": "integer", + "format": "uint32" + }, + "in": "path", + "required": true, + "deprecated": false, + "explode": true + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json; charset=utf-8": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VersionFile" + } + } + } + } + } + } + } + }, + "/website/{subdomain}/upload": { + "post": { + "summary": "Upload a new version", + "parameters": [ + { + "name": "subdomain", + "schema": { + "type": "string" + }, + "in": "path", + "required": true, + "deprecated": false, + "explode": true + }, + { + "name": "fallback", + "schema": { + "type": "string" + }, + "in": "query", + "description": "Fallback page\n\nThe fallback page gets returned when the requested page does not exist", + "required": false, + "deprecated": false, + "explode": true + }, + { + "name": "spa", + "schema": { + "type": "boolean", + "default": false + }, + "in": "query", + "description": "SPA mode (return fallback page with OK status)", + "required": false, + "deprecated": false, + "explode": true + }, + { + "name": "version_data", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "in": "query", + "description": "Associated version data\n\nThis is an arbitrary string map that can hold build information and other stuff\nand will be displayed in the site info dialog.", + "required": false, + "deprecated": false, + "explode": true + } + ], + "requestBody": { + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "" + } + }, + "security": [ + { + "ApiKeyAuthorization": [] + } + ] + } + }, + "/file/{hash}": { + "get": { + "summary": "Retrieve a file", + "parameters": [ + { + "name": "hash", + "schema": { + "type": "string" + }, + "in": "path", + "required": true, + "deprecated": false, + "explode": true + } + ], + "responses": { + "200": { + "description": "File content", + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + }, + "headers": { + "etag": { + "description": "File hash", + "required": true, + "deprecated": false, + "schema": { + "type": "string" + } + }, + "last-modified": { + "description": "Date when the file was last modified", + "required": true, + "deprecated": false, + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/info": { + "get": { + "summary": "Get information about your server", + "responses": { + "200": { + "description": "", + "content": { + "application/json; charset=utf-8": { + "schema": { + "$ref": "#/components/schemas/Info" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Info": { + "type": "object", + "description": "Server information", + "required": [ + "stats", + "version", + "uptime" + ], + "properties": { + "stats": { + "allOf": [ + { + "$ref": "#/components/schemas/Stats" + }, + { + "description": "Stats about your instance" + } + ] + }, + "version": { + "allOf": [ + { + "$ref": "#/components/schemas/VersionInfo" + }, + { + "description": "Version information" + } + ] + }, + "uptime": { + "type": "integer", + "format": "uint64", + "description": "Current uptime of the server in seconds" + } + } + }, + "SourceIcon": { + "type": "string", + "enum": [ + "link", + "git", + "github", + "gitlab", + "gitea", + "bitbucket" + ] + }, + "Stats": { + "type": "object", + "description": "Stats about your Talon instance", + "required": [ + "n_websites", + "n_files", + "storage_used" + ], + "properties": { + "n_websites": { + "type": "integer", + "format": "uint64", + "description": "Number of websites" + }, + "n_files": { + "type": "integer", + "format": "uint64", + "description": "Number of unique files" + }, + "storage_used": { + "type": "integer", + "format": "uint64", + "description": "Amount of used storage space (in bytes)" + } + } + }, + "Version": { + "type": "object", + "description": "Website version", + "required": [ + "id", + "created_at", + "data" + ], + "properties": { + "id": { + "type": "integer", + "format": "uint32", + "description": "Version ID" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Version creation date" + }, + "data": { + "type": "object", + "description": "Associated version data\n\nThis is an arbitrary string map that can hold build information and other stuff\nand will be displayed in the site info dialog.", + "additionalProperties": { + "type": "string" + } + } + } + }, + "VersionFile": { + "type": "object", + "description": "Website file", + "required": [ + "path", + "hash" + ], + "properties": { + "path": { + "type": "string", + "description": "File path" + }, + "hash": { + "type": "string", + "description": "File hash" + }, + "mime": { + "type": "string", + "description": "MIME file type" + } + } + }, + "VersionInfo": { + "type": "object", + "description": "Information about a Talon version", + "required": [ + "version", + "commit", + "commit_date", + "rust_version", + "build_target", + "build_mode" + ], + "properties": { + "version": { + "type": "string", + "description": "Talon version" + }, + "commit": { + "type": "string", + "description": "Commit hash" + }, + "commit_date": { + "type": "string", + "format": "date-time", + "description": "Commit date" + }, + "rust_version": { + "type": "string", + "description": "Rust version" + }, + "build_target": { + "type": "string", + "description": "Build target (OS and architecture)" + }, + "build_mode": { + "type": "string", + "description": "Rust build mode (`debug` / `release`)" + } + } + }, + "Visibility": { + "type": "string", + "enum": [ + "featured", + "searchable", + "hidden" + ] + }, + "Website": { + "type": "object", + "description": "Website", + "required": [ + "subdomain", + "name", + "created_at", + "visibility" + ], + "properties": { + "subdomain": { + "type": "string", + "description": "Website subdomain" + }, + "name": { + "type": "string", + "description": "Website name" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Website creation date" + }, + "latest_version": { + "type": "integer", + "format": "uint32", + "description": "Latest version ID" + }, + "color": { + "type": "string", + "description": "Color of the page icon\n\nFormat: `#7935df`" + }, + "visibility": { + "allOf": [ + { + "$ref": "#/components/schemas/Visibility" + }, + { + "description": "Visibility of the page in the sidebar menu" + } + ] + }, + "source_url": { + "type": "string", + "description": "Link to the source of the page" + }, + "source_icon": { + "allOf": [ + { + "$ref": "#/components/schemas/SourceIcon" + }, + { + "description": "Icon for the source link" + } + ] + } + } + }, + "WebsiteNew": { + "type": "object", + "description": "Create a new website", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "Website name" + }, + "color": { + "type": "string", + "description": "Color of the page icon" + }, + "visibility": { + "allOf": [ + { + "$ref": "#/components/schemas/Visibility" + }, + { + "description": "Visibility of the page in the sidebar menu", + "default": "hidden" + } + ] + }, + "source_url": { + "type": "string", + "description": "Link to the source of the page" + }, + "source_icon": { + "allOf": [ + { + "$ref": "#/components/schemas/SourceIcon" + }, + { + "description": "Icon for the source link" + } + ] + } + } + }, + "WebsiteUpdate": { + "type": "object", + "description": "Update a website with the contained values\n\nValues set to `None` remain unchanged.", + "properties": { + "name": { + "type": "string", + "description": "Website name" + }, + "color": { + "type": "string", + "description": "Color of the page icon" + }, + "visibility": { + "allOf": [ + { + "$ref": "#/components/schemas/Visibility" + }, + { + "description": "Visibility of the page in the sidebar menu" + } + ] + }, + "source_url": { + "type": "string", + "description": "Link to the source of the page" + }, + "source_icon": { + "allOf": [ + { + "$ref": "#/components/schemas/SourceIcon" + }, + { + "description": "Icon for the source link" + } + ] + } + } + } + }, + "securitySchemes": { + "ApiKeyAuthorization": { + "type": "apiKey", + "name": "X-API-Key", + "in": "header" + } + } + } +} diff --git a/openapitools.json b/openapitools.json new file mode 100644 index 0000000..0436938 --- /dev/null +++ b/openapitools.json @@ -0,0 +1,7 @@ +{ + "$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json", + "spaces": 2, + "generator-cli": { + "version": "6.4.0" + } +} diff --git a/src/api.rs b/src/api.rs index 6216371..95ed675 100644 --- a/src/api.rs +++ b/src/api.rs @@ -11,7 +11,7 @@ use poem_openapi::{ auth::ApiKey, param::{Path, Query}, payload::{Binary, Html, Json, Response}, - OpenApi, SecurityScheme, + LicenseObject, OpenApi, OpenApiService, SecurityScheme, }; use crate::{ @@ -388,3 +388,9 @@ impl TalonApi { Ok(Json(talon.info()?)) } } + +pub fn api_service() -> OpenApiService { + OpenApiService::new(TalonApi, "Talon", crate::API_VERSION) + .description("API for the Talon static site management system") + .license(LicenseObject::new("MIT License")) +} diff --git a/src/bin/openapi.rs b/src/bin/openapi.rs new file mode 100644 index 0000000..270d189 --- /dev/null +++ b/src/bin/openapi.rs @@ -0,0 +1,8 @@ +use std::{fs::File, io::Error, io::Write}; + +/// Generate Talon's OpenAPI documentation +fn main() -> Result<(), Error> { + let api = talon::api::api_service(); + let mut file = File::create("openapi.json")?; + file.write_all(api.spec().as_bytes()) +} diff --git a/src/lib.rs b/src/lib.rs index 0083484..89530f5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,8 @@ use time::OffsetDateTime; shadow_rs::shadow!(build); +pub const API_VERSION: &str = "0.1.0"; + pub static LAST_COMMIT_DATE: Lazy = Lazy::new(|| { OffsetDateTime::parse( build::COMMIT_DATE_3339, diff --git a/src/server.rs b/src/server.rs index 9507f6f..e62483c 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,7 +1,6 @@ use std::{ops::Deref, path::Path, sync::Arc}; use crate::{ - api::TalonApi, config::Config, db::Db, model::{Info, VersionInfo}, @@ -14,7 +13,6 @@ use poem::{ http::header, listener::TcpListener, middleware, Endpoint, EndpointExt, Route, RouteDomain, Server, }; -use poem_openapi::OpenApiService; use time::OffsetDateTime; #[derive(Clone)] @@ -74,8 +72,8 @@ impl Talon { } pub fn endpoint(&self) -> impl Endpoint { - let api_service = OpenApiService::new(TalonApi, "Talon", "0.1.0") - .server(format!("{}/api", self.i.cfg.server.internal_url)); + let api_service = + crate::api::api_service().server(format!("{}/api", self.i.cfg.server.internal_url)); let swagger_ui = api_service.swagger_ui(); let spec = api_service.spec(); diff --git a/ui/menu/tsconfig.json b/tsconfig.json similarity index 89% rename from ui/menu/tsconfig.json rename to tsconfig.json index 63a55db..9b5cd8e 100644 --- a/ui/menu/tsconfig.json +++ b/tsconfig.json @@ -32,5 +32,10 @@ * Use globals.d.ts instead of compilerOptions.types * to avoid limiting type declarations. */ - "include": ["globals.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"] + "include": [ + "ui/*/globals.d.ts", + "ui/*/src/**/*.ts", + "ui/*/src/**/*.js", + "ui/*/src/**/*.svelte" + ] } diff --git a/ui/menu/.eslintrc.js b/ui/menu/.eslintrc.js index 6616e9b..4ac818b 100644 --- a/ui/menu/.eslintrc.js +++ b/ui/menu/.eslintrc.js @@ -1,40 +1,40 @@ module.exports = { - extends: ["eslint:recommended"], - env: {browser: true, es6: true, node: true}, - parserOptions: { - sourceType: "module", - }, - overrides: [ - { - files: ["*.ts", "*.svelte"], - extends: [ - "eslint:recommended", - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended", - ], - globals: { - Atomics: "readonly", - SharedArrayBuffer: "readonly", - }, - parser: "@typescript-eslint/parser", - parserOptions: { - project: "./tsconfig.json", - }, - plugins: ["@typescript-eslint"], - }, - { - files: ["*.svelte"], - processor: "svelte3/svelte3", - parserOptions: { - extraFileExtensions: [".svelte"], - }, - plugins: ["svelte3", "@typescript-eslint"], - settings: { - "svelte3/typescript": true, - "svelte3/ignore-styles": () => true, - }, - }, - ], - rules: {}, - ignorePatterns: [".rollup/**", "public/**", "dist/**"], -} + extends: ["eslint:recommended"], + env: { browser: true, es6: true, node: true }, + parserOptions: { + sourceType: "module", + }, + overrides: [ + { + files: ["*.ts", "*.svelte"], + extends: [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + ], + globals: { + Atomics: "readonly", + SharedArrayBuffer: "readonly", + }, + parser: "@typescript-eslint/parser", + parserOptions: { + project: "./tsconfig.json", + }, + plugins: ["@typescript-eslint"], + }, + { + files: ["*.svelte"], + processor: "svelte3/svelte3", + parserOptions: { + extraFileExtensions: [".svelte"], + }, + plugins: ["svelte3", "@typescript-eslint"], + settings: { + "svelte3/typescript": true, + "svelte3/ignore-styles": () => true, + }, + }, + ], + rules: {}, + ignorePatterns: [".rollup/**", "public/**", "dist/**"], +}; diff --git a/ui/menu/package-lock.json b/ui/menu/package-lock.json index f19774a..621c4be 100644 --- a/ui/menu/package-lock.json +++ b/ui/menu/package-lock.json @@ -9,7 +9,8 @@ "dependencies": { "@fortawesome/free-brands-svg-icons": "^5.15.4", "@fortawesome/free-solid-svg-icons": "^5.15.4", - "minify-html-stream": "^0.3.1" + "minify-html-stream": "^0.3.1", + "talon-client": "file:../talon-client" }, "devDependencies": { "@babel/core": "^7.15.5", @@ -17,7 +18,7 @@ "@babel/preset-typescript": "^7.15.0", "@rollup/plugin-babel": "^5.3.0", "@rollup/plugin-commonjs": "^20.0.0", - "@rollup/plugin-node-resolve": "^13.0.5", + "@rollup/plugin-node-resolve": "^13.3.0", "@rollup/plugin-replace": "^3.0.0", "@rollup/plugin-typescript": "^8.2.5", "@rollup/pluginutils": "^4.1.1", @@ -41,7 +42,8 @@ "svelte-keydown": "^0.3.1", "svelte-modals": "^1.0.4", "svelte-preprocess": "^4.9.5", - "typescript": "^4.4.3" + "tslib": "^2.5.0", + "typescript": "^4.9.5" } }, "node_modules/@ampproject/remapping": { @@ -2758,9 +2760,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001460", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001460.tgz", - "integrity": "sha512-Bud7abqjvEjipUkpLs4D7gR0l8hBYBHoa+tGtKJHvT2AYzLp1z7EmVkUT4ERpVUfca8S2HGIVs883D8pUH1ZzQ==", + "version": "1.0.30001462", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001462.tgz", + "integrity": "sha512-PDd20WuOBPiasZ7KbFnmQRyuLE7cFXW2PVd7dmALzbkUXEP46upAuCDm9eY9vho8fgNMGmbAX92QBZHzcnWIqw==", "dev": true, "funding": [ { @@ -2973,9 +2975,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.320", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.320.tgz", - "integrity": "sha512-h70iRscrNluMZPVICXYl5SSB+rBKo22XfuIS1ER0OQxQZpKTnFpuS6coj7wY9M/3trv7OR88rRMOlKmRvDty7Q==", + "version": "1.4.325", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.325.tgz", + "integrity": "sha512-K1C03NT4I7BuzsRdCU5RWkgZxtswnKDYM6/eMhkEXqKu4e5T+ck610x3FPzu1y7HVFSiQKZqP16gnJzPpji1TQ==", "dev": true }, "node_modules/emoji-regex": { @@ -4489,9 +4491,9 @@ } }, "node_modules/resolve.exports": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.0.tgz", - "integrity": "sha512-6K/gDlqgQscOlg9fSRpWstA8sYe8rbELsSTNpx+3kTrsVCzvSl0zIvRErM7fdl9ERWDsKnrLnwB+Ne89918XOg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.1.tgz", + "integrity": "sha512-OEJWVeimw8mgQuj3HfkNl4KqRevH7lzeQNaWRPfx0PPse7Jk6ozcsG4FKVgtzDsC1KUF+YlTHh17NcgHOPykLw==", "dev": true, "engines": { "node": ">=10" @@ -5150,6 +5152,10 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, + "node_modules/talon-client": { + "version": "0.1.0", + "resolved": "file:../talon-client" + }, "node_modules/terser": { "version": "5.16.5", "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.5.tgz", @@ -5226,9 +5232,9 @@ } }, "node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", "dev": true }, "node_modules/tsutils": { @@ -5246,6 +5252,12 @@ "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" } }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/ui/menu/package.json b/ui/menu/package.json index 13d534d..3abb221 100644 --- a/ui/menu/package.json +++ b/ui/menu/package.json @@ -9,7 +9,7 @@ "start": "sirv public --single", "lint": "eslint .", "fix": "eslint . --fix", - "check": "svelte-check --tsconfig tsconfig.json", + "check": "svelte-check --tsconfig ../../tsconfig.json", "format": "prettier --plugin=./node_modules/prettier-plugin-svelte --write .", "pc": "npm run fix & npm run check & npm run format", "ci": "npm run lint & npm run check & prettier --plugin=./node_modules/prettier-plugin-svelte --check ." @@ -20,7 +20,7 @@ "@babel/preset-typescript": "^7.15.0", "@rollup/plugin-babel": "^5.3.0", "@rollup/plugin-commonjs": "^20.0.0", - "@rollup/plugin-node-resolve": "^13.0.5", + "@rollup/plugin-node-resolve": "^13.3.0", "@rollup/plugin-replace": "^3.0.0", "@rollup/plugin-typescript": "^8.2.5", "@rollup/pluginutils": "^4.1.1", @@ -44,11 +44,13 @@ "svelte-keydown": "^0.3.1", "svelte-modals": "^1.0.4", "svelte-preprocess": "^4.9.5", - "typescript": "^4.4.3" + "tslib": "^2.5.0", + "typescript": "^4.9.5" }, "dependencies": { "@fortawesome/free-brands-svg-icons": "^5.15.4", "@fortawesome/free-solid-svg-icons": "^5.15.4", - "minify-html-stream": "^0.3.1" + "minify-html-stream": "^0.3.1", + "talon-client": "file:../talon-client" } } diff --git a/ui/menu/public/index.html b/ui/menu/public/index.html index 8d9cd6d..73f4ccc 100644 --- a/ui/menu/public/index.html +++ b/ui/menu/public/index.html @@ -1,296 +1,199 @@ - - - + + + - - + + - + #main p { + text-align: justify; + background-color: #1f242b; + padding: 20px; + border-radius: 15px; + } + - Talon - - -
-
- - - - - - - - - - - - - - - - - - - - - -
-

development

-

- Carrot cake biscuit icing pudding danish topping powder. Croissant sugar - plum pudding halvah chocolate. Cotton candy tart cake bonbon tart. - Shortbread jelly fruitcake icing pastry. Dragée dessert cupcake cake - sesame snaps toffee pie. Sweet roll sweet roll chupa chups jelly-o - gummies tootsie roll sweet halvah oat cake. Carrot cake carrot cake - muffin bonbon sesame snaps brownie. Bonbon candy macaroon fruitcake - candy canes. Cake pudding danish liquorice cupcake jelly-o ice cream. - Liquorice lollipop danish tootsie roll toffee. Gingerbread chocolate - candy canes donut lemon drops apple pie danish bear claw. Caramels cake - jelly jelly sweet chocolate bar gingerbread icing. Cake soufflé lollipop - pudding marshmallow candy canes tootsie roll danish. -

-

- Carrot cake biscuit icing pudding danish topping powder. Croissant sugar - plum pudding halvah chocolate. Cotton candy tart cake bonbon tart. - Shortbread jelly fruitcake icing pastry. Dragée dessert cupcake cake - sesame snaps toffee pie. Sweet roll sweet roll chupa chups jelly-o - gummies tootsie roll sweet halvah oat cake. Carrot cake carrot cake - muffin bonbon sesame snaps brownie. Bonbon candy macaroon fruitcake - candy canes. Cake pudding danish liquorice cupcake jelly-o ice cream. - Liquorice lollipop danish tootsie roll toffee. Gingerbread chocolate - candy canes donut lemon drops apple pie danish bear claw. Caramels cake - jelly jelly sweet chocolate bar gingerbread icing. Cake soufflé lollipop - pudding marshmallow candy canes tootsie roll danish. -

-

- Carrot cake biscuit icing pudding danish topping powder. Croissant sugar - plum pudding halvah chocolate. Cotton candy tart cake bonbon tart. - Shortbread jelly fruitcake icing pastry. Dragée dessert cupcake cake - sesame snaps toffee pie. Sweet roll sweet roll chupa chups jelly-o - gummies tootsie roll sweet halvah oat cake. Carrot cake carrot cake - muffin bonbon sesame snaps brownie. Bonbon candy macaroon fruitcake - candy canes. Cake pudding danish liquorice cupcake jelly-o ice cream. - Liquorice lollipop danish tootsie roll toffee. Gingerbread chocolate - candy canes donut lemon drops apple pie danish bear claw. Caramels cake - jelly jelly sweet chocolate bar gingerbread icing. Cake soufflé lollipop - pudding marshmallow candy canes tootsie roll danish. -

-

- Carrot cake biscuit icing pudding danish topping powder. Croissant sugar - plum pudding halvah chocolate. Cotton candy tart cake bonbon tart. - Shortbread jelly fruitcake icing pastry. Dragée dessert cupcake cake - sesame snaps toffee pie. Sweet roll sweet roll chupa chups jelly-o - gummies tootsie roll sweet halvah oat cake. Carrot cake carrot cake - muffin bonbon sesame snaps brownie. Bonbon candy macaroon fruitcake - candy canes. Cake pudding danish liquorice cupcake jelly-o ice cream. - Liquorice lollipop danish tootsie roll toffee. Gingerbread chocolate - candy canes donut lemon drops apple pie danish bear claw. Caramels cake - jelly jelly sweet chocolate bar gingerbread icing. Cake soufflé lollipop - pudding marshmallow candy canes tootsie roll danish. -

-

- Carrot cake biscuit icing pudding danish topping powder. Croissant sugar - plum pudding halvah chocolate. Cotton candy tart cake bonbon tart. - Shortbread jelly fruitcake icing pastry. Dragée dessert cupcake cake - sesame snaps toffee pie. Sweet roll sweet roll chupa chups jelly-o - gummies tootsie roll sweet halvah oat cake. Carrot cake carrot cake - muffin bonbon sesame snaps brownie. Bonbon candy macaroon fruitcake - candy canes. Cake pudding danish liquorice cupcake jelly-o ice cream. - Liquorice lollipop danish tootsie roll toffee. Gingerbread chocolate - candy canes donut lemon drops apple pie danish bear claw. Caramels cake - jelly jelly sweet chocolate bar gingerbread icing. Cake soufflé lollipop - pudding marshmallow candy canes tootsie roll danish. -

-

- Carrot cake biscuit icing pudding danish topping powder. Croissant sugar - plum pudding halvah chocolate. Cotton candy tart cake bonbon tart. - Shortbread jelly fruitcake icing pastry. Dragée dessert cupcake cake - sesame snaps toffee pie. Sweet roll sweet roll chupa chups jelly-o - gummies tootsie roll sweet halvah oat cake. Carrot cake carrot cake - muffin bonbon sesame snaps brownie. Bonbon candy macaroon fruitcake - candy canes. Cake pudding danish liquorice cupcake jelly-o ice cream. - Liquorice lollipop danish tootsie roll toffee. Gingerbread chocolate - candy canes donut lemon drops apple pie danish bear claw. Caramels cake - jelly jelly sweet chocolate bar gingerbread icing. Cake soufflé lollipop - pudding marshmallow candy canes tootsie roll danish. -

-

- Carrot cake biscuit icing pudding danish topping powder. Croissant sugar - plum pudding halvah chocolate. Cotton candy tart cake bonbon tart. - Shortbread jelly fruitcake icing pastry. Dragée dessert cupcake cake - sesame snaps toffee pie. Sweet roll sweet roll chupa chups jelly-o - gummies tootsie roll sweet halvah oat cake. Carrot cake carrot cake - muffin bonbon sesame snaps brownie. Bonbon candy macaroon fruitcake - candy canes. Cake pudding danish liquorice cupcake jelly-o ice cream. - Liquorice lollipop danish tootsie roll toffee. Gingerbread chocolate - candy canes donut lemon drops apple pie danish bear claw. Caramels cake - jelly jelly sweet chocolate bar gingerbread icing. Cake soufflé lollipop - pudding marshmallow candy canes tootsie roll danish. -

-
- + Talon + + +
+
+ + + + + + + + + + + + + + + + + + + + + +
+

development

+

+ Carrot cake biscuit icing pudding danish topping powder. Croissant sugar plum + pudding halvah chocolate. Cotton candy tart cake bonbon tart. Shortbread jelly + fruitcake icing pastry. Dragée dessert cupcake cake sesame snaps toffee pie. + Sweet roll sweet roll chupa chups jelly-o gummies tootsie roll sweet halvah oat + cake. Carrot cake carrot cake muffin bonbon sesame snaps brownie. Bonbon candy + macaroon fruitcake candy canes. Cake pudding danish liquorice cupcake jelly-o + ice cream. Liquorice lollipop danish tootsie roll toffee. Gingerbread chocolate + candy canes donut lemon drops apple pie danish bear claw. Caramels cake jelly + jelly sweet chocolate bar gingerbread icing. Cake soufflé lollipop pudding + marshmallow candy canes tootsie roll danish. +

+

+ Carrot cake biscuit icing pudding danish topping powder. Croissant sugar plum + pudding halvah chocolate. Cotton candy tart cake bonbon tart. Shortbread jelly + fruitcake icing pastry. Dragée dessert cupcake cake sesame snaps toffee pie. + Sweet roll sweet roll chupa chups jelly-o gummies tootsie roll sweet halvah oat + cake. Carrot cake carrot cake muffin bonbon sesame snaps brownie. Bonbon candy + macaroon fruitcake candy canes. Cake pudding danish liquorice cupcake jelly-o + ice cream. Liquorice lollipop danish tootsie roll toffee. Gingerbread chocolate + candy canes donut lemon drops apple pie danish bear claw. Caramels cake jelly + jelly sweet chocolate bar gingerbread icing. Cake soufflé lollipop pudding + marshmallow candy canes tootsie roll danish. +

+

+ Carrot cake biscuit icing pudding danish topping powder. Croissant sugar plum + pudding halvah chocolate. Cotton candy tart cake bonbon tart. Shortbread jelly + fruitcake icing pastry. Dragée dessert cupcake cake sesame snaps toffee pie. + Sweet roll sweet roll chupa chups jelly-o gummies tootsie roll sweet halvah oat + cake. Carrot cake carrot cake muffin bonbon sesame snaps brownie. Bonbon candy + macaroon fruitcake candy canes. Cake pudding danish liquorice cupcake jelly-o + ice cream. Liquorice lollipop danish tootsie roll toffee. Gingerbread chocolate + candy canes donut lemon drops apple pie danish bear claw. Caramels cake jelly + jelly sweet chocolate bar gingerbread icing. Cake soufflé lollipop pudding + marshmallow candy canes tootsie roll danish. +

+

+ Carrot cake biscuit icing pudding danish topping powder. Croissant sugar plum + pudding halvah chocolate. Cotton candy tart cake bonbon tart. Shortbread jelly + fruitcake icing pastry. Dragée dessert cupcake cake sesame snaps toffee pie. + Sweet roll sweet roll chupa chups jelly-o gummies tootsie roll sweet halvah oat + cake. Carrot cake carrot cake muffin bonbon sesame snaps brownie. Bonbon candy + macaroon fruitcake candy canes. Cake pudding danish liquorice cupcake jelly-o + ice cream. Liquorice lollipop danish tootsie roll toffee. Gingerbread chocolate + candy canes donut lemon drops apple pie danish bear claw. Caramels cake jelly + jelly sweet chocolate bar gingerbread icing. Cake soufflé lollipop pudding + marshmallow candy canes tootsie roll danish. +

+

+ Carrot cake biscuit icing pudding danish topping powder. Croissant sugar plum + pudding halvah chocolate. Cotton candy tart cake bonbon tart. Shortbread jelly + fruitcake icing pastry. Dragée dessert cupcake cake sesame snaps toffee pie. + Sweet roll sweet roll chupa chups jelly-o gummies tootsie roll sweet halvah oat + cake. Carrot cake carrot cake muffin bonbon sesame snaps brownie. Bonbon candy + macaroon fruitcake candy canes. Cake pudding danish liquorice cupcake jelly-o + ice cream. Liquorice lollipop danish tootsie roll toffee. Gingerbread chocolate + candy canes donut lemon drops apple pie danish bear claw. Caramels cake jelly + jelly sweet chocolate bar gingerbread icing. Cake soufflé lollipop pudding + marshmallow candy canes tootsie roll danish. +

+

+ Carrot cake biscuit icing pudding danish topping powder. Croissant sugar plum + pudding halvah chocolate. Cotton candy tart cake bonbon tart. Shortbread jelly + fruitcake icing pastry. Dragée dessert cupcake cake sesame snaps toffee pie. + Sweet roll sweet roll chupa chups jelly-o gummies tootsie roll sweet halvah oat + cake. Carrot cake carrot cake muffin bonbon sesame snaps brownie. Bonbon candy + macaroon fruitcake candy canes. Cake pudding danish liquorice cupcake jelly-o + ice cream. Liquorice lollipop danish tootsie roll toffee. Gingerbread chocolate + candy canes donut lemon drops apple pie danish bear claw. Caramels cake jelly + jelly sweet chocolate bar gingerbread icing. Cake soufflé lollipop pudding + marshmallow candy canes tootsie roll danish. +

+

+ Carrot cake biscuit icing pudding danish topping powder. Croissant sugar plum + pudding halvah chocolate. Cotton candy tart cake bonbon tart. Shortbread jelly + fruitcake icing pastry. Dragée dessert cupcake cake sesame snaps toffee pie. + Sweet roll sweet roll chupa chups jelly-o gummies tootsie roll sweet halvah oat + cake. Carrot cake carrot cake muffin bonbon sesame snaps brownie. Bonbon candy + macaroon fruitcake candy canes. Cake pudding danish liquorice cupcake jelly-o + ice cream. Liquorice lollipop danish tootsie roll toffee. Gingerbread chocolate + candy canes donut lemon drops apple pie danish bear claw. Caramels cake jelly + jelly sweet chocolate bar gingerbread icing. Cake soufflé lollipop pudding + marshmallow candy canes tootsie roll danish. +

+
+ - - - + + diff --git a/ui/menu/rollup.config.js b/ui/menu/rollup.config.js index b66c950..8b8bba3 100644 --- a/ui/menu/rollup.config.js +++ b/ui/menu/rollup.config.js @@ -4,154 +4,154 @@ Copyright (c) 2021 stefanonepa, MIT License https://github.com/stefanonepa/svelte-component-ts */ -import svelte from "rollup-plugin-svelte" -import commonjs from "@rollup/plugin-commonjs" -import resolve from "@rollup/plugin-node-resolve" -import livereload from "rollup-plugin-livereload" -import {terser} from "rollup-plugin-terser" -import sveltePreprocess from "svelte-preprocess" -import typescript from "@rollup/plugin-typescript" -import replace from "@rollup/plugin-replace" -import babel from "@rollup/plugin-babel" +import svelte from "rollup-plugin-svelte"; +import commonjs from "@rollup/plugin-commonjs"; +import resolve from "@rollup/plugin-node-resolve"; +import livereload from "rollup-plugin-livereload"; +import { terser } from "rollup-plugin-terser"; +import sveltePreprocess from "svelte-preprocess"; +import typescript from "@rollup/plugin-typescript"; +import replace from "@rollup/plugin-replace"; +import babel from "@rollup/plugin-babel"; -import css from ".rollup/css-only" -import {serve} from ".rollup/serve" -import includeSass from ".rollup/includeSass" -import htmlMinifier from ".rollup/htmlMinifier" +import css from ".rollup/css-only"; +import { serve } from ".rollup/serve"; +import includeSass from ".rollup/includeSass"; +import htmlMinifier from ".rollup/htmlMinifier"; -const production = !process.env.ROLLUP_WATCH -const version = process.env.VERSION || "INDEV" +const production = !process.env.ROLLUP_WATCH; +const version = process.env.VERSION || "INDEV"; -const bundleName = "talon" -const bundleFile = `${bundleName}.js` -const bundleDir = production ? "dist" : "public" +const bundleName = "talon"; +const bundleFile = `${bundleName}.js`; +const bundleDir = production ? "dist" : "public"; -const appFile = "src/App.svelte" -const styleFile = "src/style/main.sass" -const extensions = [".svelte", ".ts", ".js", ".mjs"] +const appFile = "src/App.svelte"; +const styleFile = "src/style/main.sass"; +const extensions = [".svelte", ".ts", ".js", ".mjs"]; function includeCss(styles, bundle) { - const match = production ? `.shadowRoot.innerHTML="` : `.shadowRoot.innerHTML = "` + const match = production ? `.shadowRoot.innerHTML="` : `.shadowRoot.innerHTML = "`; - const currentBundle = bundle[bundleFile] - currentBundle.code = currentBundle.code.replace( - match, - `${match}` - ) + const currentBundle = bundle[bundleFile]; + currentBundle.code = currentBundle.code.replace( + match, + `${match}` + ); } export default { - input: "src/index.ts", - output: [ - { - sourcemap: !production, - format: "iife", - name: bundleName, - file: `${bundleDir}/${bundleFile}`, - plugins: [production && terser()], - }, - ], - plugins: [ - htmlMinifier({ - include: "*.svelte", - options: { - stripCarriageReturns: true, - trimLines: true, - trimElements: true, - normalizeWhiteSpace: true, - stripComments: false, - }, - }), + input: "src/index.ts", + output: [ + { + sourcemap: !production, + format: "iife", + name: bundleName, + file: `${bundleDir}/${bundleFile}`, + plugins: [production && terser()], + }, + ], + plugins: [ + htmlMinifier({ + include: "*.svelte", + options: { + stripCarriageReturns: true, + trimLines: true, + trimElements: true, + normalizeWhiteSpace: true, + stripComments: false, + }, + }), - svelte({ - preprocess: sveltePreprocess({sourceMap: !production}), - compilerOptions: { - dev: !production, - customElement: true, - tag: "talon-sidebar", - preserveWhitespace: false, - }, - emitCss: false, - include: appFile, - }), + svelte({ + preprocess: sveltePreprocess({ sourceMap: !production }), + compilerOptions: { + dev: !production, + customElement: true, + tag: "talon-sidebar", + preserveWhitespace: false, + }, + emitCss: false, + include: appFile, + }), - svelte({ - preprocess: sveltePreprocess({sourceMap: !production}), - compilerOptions: { - dev: !production, - preserveWhitespace: false, - }, - emitCss: true, - exclude: appFile, - }), + svelte({ + preprocess: sveltePreprocess({ sourceMap: !production }), + compilerOptions: { + dev: !production, + preserveWhitespace: false, + }, + emitCss: true, + exclude: appFile, + }), - css({ - output(styles, styleNodes, bundle) { - includeCss(styles, bundle) - }, - }), + css({ + output(styles, styleNodes, bundle) { + includeCss(styles, bundle); + }, + }), - includeSass({ - file: styleFile, - outputStyle: "compressed", - output: includeCss, - }), + includeSass({ + file: styleFile, + outputStyle: "compressed", + output: includeCss, + }), - resolve({ - browser: true, - dedupe: ["svelte"], - extensions, - }), - commonjs(), - typescript({ - sourceMap: !production, - inlineSources: !production, - }), + resolve({ + browser: true, + dedupe: ["svelte"], + extensions, + }), + commonjs(), + typescript({ + sourceMap: !production, + inlineSources: !production, + }), - !production && serve(), + !production && serve(), - !production && livereload(bundleDir), + !production && livereload(bundleDir), - // add transition into shadow dom - replace({ - ".ownerDocument": ".getRootNode()", - delimiters: ["", ""], - preventAssignment: true, - }), - replace({ - ".head.appendChild": ".appendChild", - delimiters: ["", ""], - preventAssignment: true, - }), + // add transition into shadow dom + replace({ + ".ownerDocument": ".getRootNode()", + delimiters: ["", ""], + preventAssignment: true, + }), + replace({ + ".head.appendChild": ".appendChild", + delimiters: ["", ""], + preventAssignment: true, + }), - // replace version placeholder - replace({ - __VERSION__: version, - preventAssignment: true, - }), + // replace version placeholder + replace({ + __VERSION__: version, + preventAssignment: true, + }), - babel({ - extensions, - exclude: "node_modules/**", - plugins: ["@babel/plugin-proposal-class-properties"], - presets: [ - [ - "@babel/preset-env", - { - modules: false, - targets: { - esmodules: true, - }, - }, - ], - "@babel/preset-typescript", - ], - babelHelpers: "bundled", - }), - ], - watch: { - chokidar: true, - clearScreen: false, - }, - external: ["./src/style/test.css"], -} + babel({ + extensions, + exclude: "node_modules/**", + plugins: ["@babel/plugin-proposal-class-properties"], + presets: [ + [ + "@babel/preset-env", + { + modules: false, + targets: { + esmodules: true, + }, + }, + ], + "@babel/preset-typescript", + ], + babelHelpers: "bundled", + }), + ], + watch: { + chokidar: true, + clearScreen: false, + }, + external: ["./src/style/test.css"], +}; diff --git a/ui/menu/src/App.svelte b/ui/menu/src/App.svelte index 1449e1a..637c8ff 100644 --- a/ui/menu/src/App.svelte +++ b/ui/menu/src/App.svelte @@ -1,16 +1,30 @@ -
- {#if isPresent} - - {/if} +
+ {#if currentWebsite} + + {/if} - -
- + + +
+
diff --git a/ui/menu/src/components/ImageIcon.svelte b/ui/menu/src/components/ImageIcon.svelte index 98d3c78..055e74b 100644 --- a/ui/menu/src/components/ImageIcon.svelte +++ b/ui/menu/src/components/ImageIcon.svelte @@ -1,9 +1,9 @@ @@ -19,13 +19,13 @@ - {#if imageSrc} - - {:else} - - {alt} - - {/if} + {#if imageSrc} + + {:else} + + {alt} + + {/if} diff --git a/ui/menu/src/components/InfoModal.svelte b/ui/menu/src/components/InfoModal.svelte index f071962..bd7a107 100644 --- a/ui/menu/src/components/InfoModal.svelte +++ b/ui/menu/src/components/InfoModal.svelte @@ -1,63 +1,38 @@ diff --git a/ui/menu/src/components/MenuItemPage.svelte b/ui/menu/src/components/MenuItemPage.svelte index 026d806..c9199ef 100644 --- a/ui/menu/src/components/MenuItemPage.svelte +++ b/ui/menu/src/components/MenuItemPage.svelte @@ -1,22 +1,22 @@ - - + + diff --git a/ui/menu/src/components/PageIcon.svelte b/ui/menu/src/components/PageIcon.svelte index e783260..171271e 100644 --- a/ui/menu/src/components/PageIcon.svelte +++ b/ui/menu/src/components/PageIcon.svelte @@ -1,19 +1,19 @@ -{#if page} +{#if website} {:else} diff --git a/ui/menu/src/index.ts b/ui/menu/src/index.ts index 92b9faa..1f491da 100644 --- a/ui/menu/src/index.ts +++ b/ui/menu/src/index.ts @@ -1,4 +1,4 @@ -const sidebar = document.createElement("talon-sidebar") -document.body.append(sidebar) +const sidebar = document.createElement("talon-sidebar"); +document.body.append(sidebar); -export {default as default} from "./App.svelte" +export { default as default } from "./App.svelte"; diff --git a/ui/menu/src/util/api.ts b/ui/menu/src/util/api.ts new file mode 100644 index 0000000..df61a15 --- /dev/null +++ b/ui/menu/src/util/api.ts @@ -0,0 +1,18 @@ +import {Writable, writable} from "svelte/store" + +import {Configuration, DefaultApi, Website} from "talon-client" +import {getSubdomain} from "./functions" +import { talonConfig } from "./talonData" + +export const websitesStore: Writable = writable([]) +export const currentWebsiteStore: Writable = writable(null) + +export const client = new DefaultApi(new Configuration({basePath: talonConfig.api})); + +export function fetchWebsites(): void { + client.websitesGet().then((ws) => { + websitesStore.set(ws) + const subdomain = getSubdomain() + currentWebsiteStore.set(ws.find((w) => w.subdomain == subdomain)) + }) +} diff --git a/ui/menu/src/util/functions.ts b/ui/menu/src/util/functions.ts index 31da503..3d0d3e2 100644 --- a/ui/menu/src/util/functions.ts +++ b/ui/menu/src/util/functions.ts @@ -1,5 +1,36 @@ -function formatDate(dateString: string): string { - return new Date(dateString).toLocaleString(navigator.language) +import {talonConfig} from "./talonData" + +export function formatDate(date: Date): string { + return date.toLocaleString(navigator.language) } -export {formatDate} +export function getSubdomain(): string { + const hn = window.location.hostname + const rd_noport = talonConfig.root_domain.split(":", 1)[0] + + if (hn.endsWith("." + rd_noport)) { + return hn.substring(0, hn.length - rd_noport.length - 1) + } + return "-" +} + +export function getWebsiteUrl(subdomain: string): string { + const proto = window.location.protocol + + if (subdomain === "-") { + return `${proto}//${talonConfig.root_domain}` + } else { + return `${proto}//${subdomain}.${talonConfig.root_domain}` + } +} + +export function getWebsiteVersionUrl(subdomain: string, version: number): string { + const proto = window.location.protocol + const v = `--v${version}` + + if (subdomain === "-") { + return `${proto}//x${v}.${talonConfig.root_domain}` + } else { + return `${proto}//${subdomain}${v}.${talonConfig.root_domain}` + } +} diff --git a/ui/menu/src/util/talonData.ts b/ui/menu/src/util/talonData.ts index 13e4b14..5189cef 100644 --- a/ui/menu/src/util/talonData.ts +++ b/ui/menu/src/util/talonData.ts @@ -1,54 +1,33 @@ -import type {TalonData, TalonPage, TalonVersion} from "./types" -import {TalonVisibility} from "./types" +import type {TalonConfig} from "./types" -const talonData: TalonData = JSON.parse( - document.getElementById("talon-data").textContent -) as TalonData +export const talonConfig: TalonConfig = JSON.parse( + document.getElementById("talon-config").textContent +) as TalonConfig -const isTalonData = (obj: TalonData) => - "root_path" in obj && - "current_page" in obj && - "current_version" in obj && - "versions" in obj && - "pages" in obj +// const rootPath = talonData.root_path -const isTalonVersion = (obj: TalonVersion) => "date" in obj && "user" in obj +// const currentVersion = talonData.versions[talonData.current_version] +// const currentVersionId = talonData.current_version -const rootPath = talonData.root_path +// const getCurrentPage = () => { +// if (talonData.current_page) { +// return talonData.pages[talonData.current_page] +// } else { +// return { +// name: "#" + talonData.current_version, +// path: "&v/" + talonData.current_version, +// color: "#7935df", +// visibility: TalonVisibility.HIDDEN, +// image: undefined, +// source: undefined, +// } +// } +// } -const currentVersion = talonData.versions[talonData.current_version] -const currentVersionId = talonData.current_version +// const currentPage: TalonPage = getCurrentPage() +// const currentPageId = talonData.current_page -const getCurrentPage = () => { - if (talonData.current_page) { - return talonData.pages[talonData.current_page] - } else { - return { - name: "#" + talonData.current_version, - path: "&v/" + talonData.current_version, - color: "#7935df", - visibility: TalonVisibility.HIDDEN, - image: undefined, - source: undefined, - } - } -} +// const isPresent = isTalonData(talonData) && isTalonVersion(currentVersion) -const currentPage: TalonPage = getCurrentPage() -const currentPageId = talonData.current_page - -const isPresent = isTalonData(talonData) && isTalonVersion(currentVersion) - -const versions = talonData.versions -const pages = talonData.pages - -export { - rootPath, - isPresent, - currentVersion, - currentVersionId, - currentPage, - currentPageId, - versions, - pages, -} +// const versions = talonData.versions +// const pages = talonData.pages diff --git a/ui/menu/src/util/types.ts b/ui/menu/src/util/types.ts index 47b69e7..0c5fb17 100644 --- a/ui/menu/src/util/types.ts +++ b/ui/menu/src/util/types.ts @@ -1,54 +1,15 @@ -export interface TalonData { - root_path: string - current_page: string | null - current_version: string - versions: {[key: string]: TalonVersion} - pages: {[key: string]: TalonPage} +export interface TalonConfig { + api: string; + version: string; + root_domain: string; } - -export interface TalonPage { - name: string - path: string - color: string - visibility: TalonVisibility - image: string | undefined - source: TalonLink | undefined -} - -export enum TalonVisibility { - FEATURED = "featured", - SEARCHABLE = "searchable", - HIDDEN = "hidden", -} - -export interface TalonLink { - url: string - type: TalonLinkType -} - -export enum TalonLinkType { - LINK = "link", - GIT = "git", - GITHUB = "github", - GITLAB = "gitlab", - GITEA = "gitea", - BITBUCKET = "bitbucket", -} - -export interface TalonVersion { - date: string - name: string | undefined - user: string - tags: {[key: string]: string} | undefined -} - export interface Focusable { - focus(): void - blur(): void + focus(): void; + blur(): void; } export interface SvelteActionRes { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - update?: (parameters: any) => void - destroy?: () => void + // eslint-disable-next-line @typescript-eslint/no-explicit-any + update?: (parameters: any) => void; + destroy?: () => void; } diff --git a/ui/menu/testdata/test.json b/ui/menu/testdata/test.json deleted file mode 100644 index 6481b55..0000000 --- a/ui/menu/testdata/test.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "root_path": "/", - "current_page": null, - "current_version": "6", - "versions": { - "3": { - "date": "2021-06-15T11:12:21+00:00", - "name": "0.1.0", - "user": "ThetaDev", - "tags": { - "commit": "ec55eba5ae45640c3d225c2471920fd3c9a36489" - } - }, - "4": { - "date": "2021-06-20T16:48:21+00:00", - "name": "0.1.1", - "user": "ThetaDev", - "tags": { - "commit": "ec55eba5ae45640c3d225c2471920fd3c9a36489" - } - }, - "5": { - "date": "2021-06-22T12:08:21+00:00", - "name": "0.1.2", - "user": "ThetaDev", - "tags": { - "commit": "ec55eba5ae45640c3d225c2471920fd3c9a36489" - } - }, - "6": { - "date": "2021-07-02T16:12:40+00:00", - "name": "0.1.3", - "user": "ThetaDev", - "tags": { - "commit": "bbc7342580b48433481857bfe95e58784a508275" - } - } - }, - "pages": { - "1": { - "name": "ThetaDev", - "path": "", - "color": "#1f91ee", - "visibility": "featured", - "source": { - "url": "https://github.com/Theta-Dev", - "type": "github" - } - }, - "2": { - "name": "Talon", - "path": "Talon", - "color": "#4b228a", - "visibility": "featured", - "source": { - "url": "https://github.com/Theta-Dev/Talon", - "type": "github" - } - }, - "3": { - "name": "Spotify-Gender-Ex", - "path": "Spotify-Gender-Ex", - "color": "#1DB954", - "image": "https://raw.githubusercontent.com/Theta-Dev/Spotify-Gender-Ex/master/assets/logo_square.svg", - "visibility": "featured", - "source": { - "url": "https://github.com/Theta-Dev/Spotify-Gender-Ex", - "type": "github" - } - }, - "4": { - "name": "A1", - "path": "tests/a1", - "color": "#ff0000", - "visibility": "searchable" - }, - "5": { - "name": "B1", - "path": "tests/b1", - "color": "#00ff00", - "visibility": "searchable" - }, - "6": { - "name": "C1", - "path": "tests/c1", - "color": "#0000ff", - "visibility": "searchable" - } - } -} diff --git a/ui/menu/testdata/testSchema.json b/ui/menu/testdata/testSchema.json deleted file mode 100644 index c62a091..0000000 --- a/ui/menu/testdata/testSchema.json +++ /dev/null @@ -1,99 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Talon menu data", - "definitions": { - "Version": { - "type": "object", - "properties": { - "date": { - "type": "string", - "format": "date-time" - }, - "name": { - "type": "string" - }, - "user": { - "type": "string" - }, - "tags": { - "type": "object", - "additionalProperties": { - "type": "string" - } - } - }, - "required": ["date", "user"] - }, - "Link": { - "type": "object", - "properties": { - "url": { - "type": "string" - }, - "type": { - "type": "string", - "enum": ["link", "git", "github", "gitlab", "gitea", "bitbucket"] - } - }, - "required": ["url", "type"] - }, - "Page": { - "type": "object", - "properties": { - "name": { - "type": "string", - "maxLength": 100 - }, - "path": { - "type": "string" - }, - "color": { - "type": "string", - "format": "regex", - "pattern": "^#[A-z\\d]{6}$" - }, - "image": { - "type": "string", - "format": "uri" - }, - "visibility": { - "type": "string", - "enum": ["featured", "searchable", "hidden"] - }, - "source": { - "$ref": "#/definitions/Link" - } - }, - "required": ["name", "path", "color", "visibility"] - } - }, - "oneOf": [ - { - "type": "object", - "properties": { - "root_path": { - "type": "string" - }, - "current_page": { - "type": ["string", "null"] - }, - "current_version": { - "type": "string" - }, - "versions": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Version" - } - }, - "pages": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Page" - } - } - }, - "required": ["root_path", "current_page", "current_version", "versions", "pages"] - } - ] -} diff --git a/ui/talon-client/.gitignore b/ui/talon-client/.gitignore new file mode 100644 index 0000000..149b576 --- /dev/null +++ b/ui/talon-client/.gitignore @@ -0,0 +1,4 @@ +wwwroot/*.js +node_modules +typings +dist diff --git a/ui/talon-client/.npmignore b/ui/talon-client/.npmignore new file mode 100644 index 0000000..b43bf86 --- /dev/null +++ b/ui/talon-client/.npmignore @@ -0,0 +1 @@ +README.md diff --git a/ui/talon-client/.openapi-generator-ignore b/ui/talon-client/.openapi-generator-ignore new file mode 100644 index 0000000..7484ee5 --- /dev/null +++ b/ui/talon-client/.openapi-generator-ignore @@ -0,0 +1,23 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/ui/talon-client/.openapi-generator/FILES b/ui/talon-client/.openapi-generator/FILES new file mode 100644 index 0000000..4acdeed --- /dev/null +++ b/ui/talon-client/.openapi-generator/FILES @@ -0,0 +1,23 @@ +.gitignore +.npmignore +.openapi-generator-ignore +README.md +package.json +src/apis/DefaultApi.ts +src/apis/index.ts +src/index.ts +src/models/Info.ts +src/models/InfoStats.ts +src/models/InfoVersion.ts +src/models/SourceIcon.ts +src/models/Stats.ts +src/models/Version.ts +src/models/VersionFile.ts +src/models/VersionInfo.ts +src/models/Visibility.ts +src/models/Website.ts +src/models/WebsiteNew.ts +src/models/WebsiteUpdate.ts +src/models/index.ts +src/runtime.ts +tsconfig.json diff --git a/ui/talon-client/.openapi-generator/VERSION b/ui/talon-client/.openapi-generator/VERSION new file mode 100644 index 0000000..19b860c --- /dev/null +++ b/ui/talon-client/.openapi-generator/VERSION @@ -0,0 +1 @@ +6.4.0 diff --git a/ui/talon-client/README.md b/ui/talon-client/README.md new file mode 100644 index 0000000..03f9152 --- /dev/null +++ b/ui/talon-client/README.md @@ -0,0 +1,45 @@ +## talon-client@0.1.0 + +This generator creates TypeScript/JavaScript client that utilizes [Fetch API](https://fetch.spec.whatwg.org/). The generated Node module can be used in the following environments: + +Environment +* Node.js +* Webpack +* Browserify + +Language level +* ES5 - you must have a Promises/A+ library installed +* ES6 + +Module system +* CommonJS +* ES6 module system + +It can be used in both TypeScript and JavaScript. In TypeScript, the definition should be automatically resolved via `package.json`. ([Reference](http://www.typescriptlang.org/docs/handbook/typings-for-npm-packages.html)) + +### Building + +To build and compile the typescript sources to javascript use: +``` +npm install +npm run build +``` + +### Publishing + +First build the package then run ```npm publish``` + +### Consuming + +navigate to the folder of your consuming project and run one of the following commands. + +_published:_ + +``` +npm install talon-client@0.1.0 --save +``` + +_unPublished (not recommended):_ + +``` +npm install PATH_TO_GENERATED_PACKAGE --save diff --git a/ui/talon-client/package-lock.json b/ui/talon-client/package-lock.json new file mode 100644 index 0000000..7816643 --- /dev/null +++ b/ui/talon-client/package-lock.json @@ -0,0 +1,28 @@ +{ + "name": "talon-client", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "talon-client", + "version": "0.1.0", + "devDependencies": { + "typescript": "^4.0" + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + } + } +} diff --git a/ui/talon-client/package.json b/ui/talon-client/package.json new file mode 100644 index 0000000..a459cd3 --- /dev/null +++ b/ui/talon-client/package.json @@ -0,0 +1,19 @@ +{ + "name": "talon-client", + "version": "0.1.0", + "description": "OpenAPI client for talon-client", + "author": "OpenAPI-Generator", + "repository": { + "type": "git", + "url": "https://github.com/GIT_USER_ID/GIT_REPO_ID.git" + }, + "main": "./dist/index.js", + "typings": "./dist/index.d.ts", + "scripts": { + "build": "tsc", + "prepare": "npm run build" + }, + "devDependencies": { + "typescript": "^4.0" + } +} diff --git a/ui/talon-client/src/apis/DefaultApi.ts b/ui/talon-client/src/apis/DefaultApi.ts new file mode 100644 index 0000000..c5c159a --- /dev/null +++ b/ui/talon-client/src/apis/DefaultApi.ts @@ -0,0 +1,559 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Talon + * API for the Talon static site management system + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import * as runtime from '../runtime'; +import type { + Info, + Version, + VersionFile, + Visibility, + Website, + WebsiteNew, + WebsiteUpdate, +} from '../models'; +import { + InfoFromJSON, + InfoToJSON, + VersionFromJSON, + VersionToJSON, + VersionFileFromJSON, + VersionFileToJSON, + VisibilityFromJSON, + VisibilityToJSON, + WebsiteFromJSON, + WebsiteToJSON, + WebsiteNewFromJSON, + WebsiteNewToJSON, + WebsiteUpdateFromJSON, + WebsiteUpdateToJSON, +} from '../models'; + +export interface FileHashGetRequest { + hash: string; +} + +export interface WebsiteSubdomainDeleteRequest { + subdomain: string; +} + +export interface WebsiteSubdomainGetRequest { + subdomain: string; +} + +export interface WebsiteSubdomainPatchRequest { + subdomain: string; + websiteUpdate: WebsiteUpdate; +} + +export interface WebsiteSubdomainPutRequest { + subdomain: string; + websiteNew: WebsiteNew; +} + +export interface WebsiteSubdomainUploadPostRequest { + subdomain: string; + body: Blob; + fallback?: string; + spa?: boolean; + versionData?: { [key: string]: string; }; +} + +export interface WebsiteSubdomainVersionVersionDeleteRequest { + subdomain: string; + version: number; +} + +export interface WebsiteSubdomainVersionVersionFilesGetRequest { + subdomain: string; + version: number; +} + +export interface WebsiteSubdomainVersionVersionGetRequest { + subdomain: string; + version: number; +} + +export interface WebsiteSubdomainVersionsGetRequest { + subdomain: string; +} + +export interface WebsitesAllGetRequest { + visibility?: Visibility; +} + +export interface WebsitesGetRequest { + visibility?: Visibility; +} + +/** + * + */ +export class DefaultApi extends runtime.BaseAPI { + + /** + * Retrieve a file + */ + async fileHashGetRaw(requestParameters: FileHashGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + if (requestParameters.hash === null || requestParameters.hash === undefined) { + throw new runtime.RequiredError('hash','Required parameter requestParameters.hash was null or undefined when calling fileHashGet.'); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + const response = await this.request({ + path: `/file/{hash}`.replace(`{${"hash"}}`, encodeURIComponent(String(requestParameters.hash))), + method: 'GET', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + return new runtime.BlobApiResponse(response); + } + + /** + * Retrieve a file + */ + async fileHashGet(requestParameters: FileHashGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + const response = await this.fileHashGetRaw(requestParameters, initOverrides); + return await response.value(); + } + + /** + * Get information about your server + */ + async infoGetRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + const response = await this.request({ + path: `/info`, + method: 'GET', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + return new runtime.JSONApiResponse(response, (jsonValue) => InfoFromJSON(jsonValue)); + } + + /** + * Get information about your server + */ + async infoGet(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + const response = await this.infoGetRaw(initOverrides); + return await response.value(); + } + + /** + * Delete website + */ + async websiteSubdomainDeleteRaw(requestParameters: WebsiteSubdomainDeleteRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + if (requestParameters.subdomain === null || requestParameters.subdomain === undefined) { + throw new runtime.RequiredError('subdomain','Required parameter requestParameters.subdomain was null or undefined when calling websiteSubdomainDelete.'); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + if (this.configuration && this.configuration.apiKey) { + headerParameters["X-API-Key"] = this.configuration.apiKey("X-API-Key"); // ApiKeyAuthorization authentication + } + + const response = await this.request({ + path: `/website/{subdomain}`.replace(`{${"subdomain"}}`, encodeURIComponent(String(requestParameters.subdomain))), + method: 'DELETE', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + return new runtime.VoidApiResponse(response); + } + + /** + * Delete website + */ + async websiteSubdomainDelete(requestParameters: WebsiteSubdomainDeleteRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + await this.websiteSubdomainDeleteRaw(requestParameters, initOverrides); + } + + /** + * Get a website + */ + async websiteSubdomainGetRaw(requestParameters: WebsiteSubdomainGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + if (requestParameters.subdomain === null || requestParameters.subdomain === undefined) { + throw new runtime.RequiredError('subdomain','Required parameter requestParameters.subdomain was null or undefined when calling websiteSubdomainGet.'); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + const response = await this.request({ + path: `/website/{subdomain}`.replace(`{${"subdomain"}}`, encodeURIComponent(String(requestParameters.subdomain))), + method: 'GET', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + return new runtime.JSONApiResponse(response, (jsonValue) => WebsiteFromJSON(jsonValue)); + } + + /** + * Get a website + */ + async websiteSubdomainGet(requestParameters: WebsiteSubdomainGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + const response = await this.websiteSubdomainGetRaw(requestParameters, initOverrides); + return await response.value(); + } + + /** + * Update website + */ + async websiteSubdomainPatchRaw(requestParameters: WebsiteSubdomainPatchRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + if (requestParameters.subdomain === null || requestParameters.subdomain === undefined) { + throw new runtime.RequiredError('subdomain','Required parameter requestParameters.subdomain was null or undefined when calling websiteSubdomainPatch.'); + } + + if (requestParameters.websiteUpdate === null || requestParameters.websiteUpdate === undefined) { + throw new runtime.RequiredError('websiteUpdate','Required parameter requestParameters.websiteUpdate was null or undefined when calling websiteSubdomainPatch.'); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters['Content-Type'] = 'application/json; charset=utf-8'; + + if (this.configuration && this.configuration.apiKey) { + headerParameters["X-API-Key"] = this.configuration.apiKey("X-API-Key"); // ApiKeyAuthorization authentication + } + + const response = await this.request({ + path: `/website/{subdomain}`.replace(`{${"subdomain"}}`, encodeURIComponent(String(requestParameters.subdomain))), + method: 'PATCH', + headers: headerParameters, + query: queryParameters, + body: WebsiteUpdateToJSON(requestParameters.websiteUpdate), + }, initOverrides); + + return new runtime.VoidApiResponse(response); + } + + /** + * Update website + */ + async websiteSubdomainPatch(requestParameters: WebsiteSubdomainPatchRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + await this.websiteSubdomainPatchRaw(requestParameters, initOverrides); + } + + /** + * Create a new website + */ + async websiteSubdomainPutRaw(requestParameters: WebsiteSubdomainPutRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + if (requestParameters.subdomain === null || requestParameters.subdomain === undefined) { + throw new runtime.RequiredError('subdomain','Required parameter requestParameters.subdomain was null or undefined when calling websiteSubdomainPut.'); + } + + if (requestParameters.websiteNew === null || requestParameters.websiteNew === undefined) { + throw new runtime.RequiredError('websiteNew','Required parameter requestParameters.websiteNew was null or undefined when calling websiteSubdomainPut.'); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters['Content-Type'] = 'application/json; charset=utf-8'; + + if (this.configuration && this.configuration.apiKey) { + headerParameters["X-API-Key"] = this.configuration.apiKey("X-API-Key"); // ApiKeyAuthorization authentication + } + + const response = await this.request({ + path: `/website/{subdomain}`.replace(`{${"subdomain"}}`, encodeURIComponent(String(requestParameters.subdomain))), + method: 'PUT', + headers: headerParameters, + query: queryParameters, + body: WebsiteNewToJSON(requestParameters.websiteNew), + }, initOverrides); + + return new runtime.VoidApiResponse(response); + } + + /** + * Create a new website + */ + async websiteSubdomainPut(requestParameters: WebsiteSubdomainPutRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + await this.websiteSubdomainPutRaw(requestParameters, initOverrides); + } + + /** + * Upload a new version + */ + async websiteSubdomainUploadPostRaw(requestParameters: WebsiteSubdomainUploadPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + if (requestParameters.subdomain === null || requestParameters.subdomain === undefined) { + throw new runtime.RequiredError('subdomain','Required parameter requestParameters.subdomain was null or undefined when calling websiteSubdomainUploadPost.'); + } + + if (requestParameters.body === null || requestParameters.body === undefined) { + throw new runtime.RequiredError('body','Required parameter requestParameters.body was null or undefined when calling websiteSubdomainUploadPost.'); + } + + const queryParameters: any = {}; + + if (requestParameters.fallback !== undefined) { + queryParameters['fallback'] = requestParameters.fallback; + } + + if (requestParameters.spa !== undefined) { + queryParameters['spa'] = requestParameters.spa; + } + + if (requestParameters.versionData !== undefined) { + queryParameters['version_data'] = requestParameters.versionData; + } + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters['Content-Type'] = 'application/octet-stream'; + + if (this.configuration && this.configuration.apiKey) { + headerParameters["X-API-Key"] = this.configuration.apiKey("X-API-Key"); // ApiKeyAuthorization authentication + } + + const response = await this.request({ + path: `/website/{subdomain}/upload`.replace(`{${"subdomain"}}`, encodeURIComponent(String(requestParameters.subdomain))), + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: requestParameters.body as any, + }, initOverrides); + + return new runtime.VoidApiResponse(response); + } + + /** + * Upload a new version + */ + async websiteSubdomainUploadPost(requestParameters: WebsiteSubdomainUploadPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + await this.websiteSubdomainUploadPostRaw(requestParameters, initOverrides); + } + + /** + * Delete version + */ + async websiteSubdomainVersionVersionDeleteRaw(requestParameters: WebsiteSubdomainVersionVersionDeleteRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + if (requestParameters.subdomain === null || requestParameters.subdomain === undefined) { + throw new runtime.RequiredError('subdomain','Required parameter requestParameters.subdomain was null or undefined when calling websiteSubdomainVersionVersionDelete.'); + } + + if (requestParameters.version === null || requestParameters.version === undefined) { + throw new runtime.RequiredError('version','Required parameter requestParameters.version was null or undefined when calling websiteSubdomainVersionVersionDelete.'); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + if (this.configuration && this.configuration.apiKey) { + headerParameters["X-API-Key"] = this.configuration.apiKey("X-API-Key"); // ApiKeyAuthorization authentication + } + + const response = await this.request({ + path: `/website/{subdomain}/version/{version}`.replace(`{${"subdomain"}}`, encodeURIComponent(String(requestParameters.subdomain))).replace(`{${"version"}}`, encodeURIComponent(String(requestParameters.version))), + method: 'DELETE', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + return new runtime.VoidApiResponse(response); + } + + /** + * Delete version + */ + async websiteSubdomainVersionVersionDelete(requestParameters: WebsiteSubdomainVersionVersionDeleteRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + await this.websiteSubdomainVersionVersionDeleteRaw(requestParameters, initOverrides); + } + + /** + * Get version files + */ + async websiteSubdomainVersionVersionFilesGetRaw(requestParameters: WebsiteSubdomainVersionVersionFilesGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>> { + if (requestParameters.subdomain === null || requestParameters.subdomain === undefined) { + throw new runtime.RequiredError('subdomain','Required parameter requestParameters.subdomain was null or undefined when calling websiteSubdomainVersionVersionFilesGet.'); + } + + if (requestParameters.version === null || requestParameters.version === undefined) { + throw new runtime.RequiredError('version','Required parameter requestParameters.version was null or undefined when calling websiteSubdomainVersionVersionFilesGet.'); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + const response = await this.request({ + path: `/website/{subdomain}/version/{version}/files`.replace(`{${"subdomain"}}`, encodeURIComponent(String(requestParameters.subdomain))).replace(`{${"version"}}`, encodeURIComponent(String(requestParameters.version))), + method: 'GET', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + return new runtime.JSONApiResponse(response, (jsonValue) => jsonValue.map(VersionFileFromJSON)); + } + + /** + * Get version files + */ + async websiteSubdomainVersionVersionFilesGet(requestParameters: WebsiteSubdomainVersionVersionFilesGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + const response = await this.websiteSubdomainVersionVersionFilesGetRaw(requestParameters, initOverrides); + return await response.value(); + } + + /** + * Get version + */ + async websiteSubdomainVersionVersionGetRaw(requestParameters: WebsiteSubdomainVersionVersionGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + if (requestParameters.subdomain === null || requestParameters.subdomain === undefined) { + throw new runtime.RequiredError('subdomain','Required parameter requestParameters.subdomain was null or undefined when calling websiteSubdomainVersionVersionGet.'); + } + + if (requestParameters.version === null || requestParameters.version === undefined) { + throw new runtime.RequiredError('version','Required parameter requestParameters.version was null or undefined when calling websiteSubdomainVersionVersionGet.'); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + const response = await this.request({ + path: `/website/{subdomain}/version/{version}`.replace(`{${"subdomain"}}`, encodeURIComponent(String(requestParameters.subdomain))).replace(`{${"version"}}`, encodeURIComponent(String(requestParameters.version))), + method: 'GET', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + return new runtime.JSONApiResponse(response, (jsonValue) => VersionFromJSON(jsonValue)); + } + + /** + * Get version + */ + async websiteSubdomainVersionVersionGet(requestParameters: WebsiteSubdomainVersionVersionGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + const response = await this.websiteSubdomainVersionVersionGetRaw(requestParameters, initOverrides); + return await response.value(); + } + + /** + * Get website versions + */ + async websiteSubdomainVersionsGetRaw(requestParameters: WebsiteSubdomainVersionsGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>> { + if (requestParameters.subdomain === null || requestParameters.subdomain === undefined) { + throw new runtime.RequiredError('subdomain','Required parameter requestParameters.subdomain was null or undefined when calling websiteSubdomainVersionsGet.'); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + const response = await this.request({ + path: `/website/{subdomain}/versions`.replace(`{${"subdomain"}}`, encodeURIComponent(String(requestParameters.subdomain))), + method: 'GET', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + return new runtime.JSONApiResponse(response, (jsonValue) => jsonValue.map(VersionFromJSON)); + } + + /** + * Get website versions + */ + async websiteSubdomainVersionsGet(requestParameters: WebsiteSubdomainVersionsGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + const response = await this.websiteSubdomainVersionsGetRaw(requestParameters, initOverrides); + return await response.value(); + } + + /** + * Returns all websites from Talon\'s database (including hidden ones, if the current user has access to them). This endpoint requires authentication (use the `/websites` endpoint for unauthenticated users). + * Get all websites + */ + async websitesAllGetRaw(requestParameters: WebsitesAllGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>> { + const queryParameters: any = {}; + + if (requestParameters.visibility !== undefined) { + queryParameters['visibility'] = requestParameters.visibility; + } + + const headerParameters: runtime.HTTPHeaders = {}; + + if (this.configuration && this.configuration.apiKey) { + headerParameters["X-API-Key"] = this.configuration.apiKey("X-API-Key"); // ApiKeyAuthorization authentication + } + + const response = await this.request({ + path: `/websitesAll`, + method: 'GET', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + return new runtime.JSONApiResponse(response, (jsonValue) => jsonValue.map(WebsiteFromJSON)); + } + + /** + * Returns all websites from Talon\'s database (including hidden ones, if the current user has access to them). This endpoint requires authentication (use the `/websites` endpoint for unauthenticated users). + * Get all websites + */ + async websitesAllGet(requestParameters: WebsitesAllGetRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + const response = await this.websitesAllGetRaw(requestParameters, initOverrides); + return await response.value(); + } + + /** + * Returns all publicly listed websites (visibility != `hidden`) + * Get all publicly listed websites + */ + async websitesGetRaw(requestParameters: WebsitesGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise>> { + const queryParameters: any = {}; + + if (requestParameters.visibility !== undefined) { + queryParameters['visibility'] = requestParameters.visibility; + } + + const headerParameters: runtime.HTTPHeaders = {}; + + const response = await this.request({ + path: `/websites`, + method: 'GET', + headers: headerParameters, + query: queryParameters, + }, initOverrides); + + return new runtime.JSONApiResponse(response, (jsonValue) => jsonValue.map(WebsiteFromJSON)); + } + + /** + * Returns all publicly listed websites (visibility != `hidden`) + * Get all publicly listed websites + */ + async websitesGet(requestParameters: WebsitesGetRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + const response = await this.websitesGetRaw(requestParameters, initOverrides); + return await response.value(); + } + +} diff --git a/ui/talon-client/src/apis/index.ts b/ui/talon-client/src/apis/index.ts new file mode 100644 index 0000000..69c44c0 --- /dev/null +++ b/ui/talon-client/src/apis/index.ts @@ -0,0 +1,3 @@ +/* tslint:disable */ +/* eslint-disable */ +export * from './DefaultApi'; diff --git a/ui/talon-client/src/index.ts b/ui/talon-client/src/index.ts new file mode 100644 index 0000000..be9d1ed --- /dev/null +++ b/ui/talon-client/src/index.ts @@ -0,0 +1,5 @@ +/* tslint:disable */ +/* eslint-disable */ +export * from './runtime'; +export * from './apis'; +export * from './models'; diff --git a/ui/talon-client/src/models/Info.ts b/ui/talon-client/src/models/Info.ts new file mode 100644 index 0000000..db4f551 --- /dev/null +++ b/ui/talon-client/src/models/Info.ts @@ -0,0 +1,96 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Talon + * API for the Talon static site management system + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +import type { InfoStats } from './InfoStats'; +import { + InfoStatsFromJSON, + InfoStatsFromJSONTyped, + InfoStatsToJSON, +} from './InfoStats'; +import type { InfoVersion } from './InfoVersion'; +import { + InfoVersionFromJSON, + InfoVersionFromJSONTyped, + InfoVersionToJSON, +} from './InfoVersion'; + +/** + * Server information + * @export + * @interface Info + */ +export interface Info { + /** + * + * @type {InfoStats} + * @memberof Info + */ + stats: InfoStats; + /** + * + * @type {InfoVersion} + * @memberof Info + */ + version: InfoVersion; + /** + * Current uptime of the server in seconds + * @type {number} + * @memberof Info + */ + uptime: number; +} + +/** + * Check if a given object implements the Info interface. + */ +export function instanceOfInfo(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "stats" in value; + isInstance = isInstance && "version" in value; + isInstance = isInstance && "uptime" in value; + + return isInstance; +} + +export function InfoFromJSON(json: any): Info { + return InfoFromJSONTyped(json, false); +} + +export function InfoFromJSONTyped(json: any, ignoreDiscriminator: boolean): Info { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'stats': InfoStatsFromJSON(json['stats']), + 'version': InfoVersionFromJSON(json['version']), + 'uptime': json['uptime'], + }; +} + +export function InfoToJSON(value?: Info | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'stats': InfoStatsToJSON(value.stats), + 'version': InfoVersionToJSON(value.version), + 'uptime': value.uptime, + }; +} diff --git a/ui/talon-client/src/models/InfoStats.ts b/ui/talon-client/src/models/InfoStats.ts new file mode 100644 index 0000000..e01001f --- /dev/null +++ b/ui/talon-client/src/models/InfoStats.ts @@ -0,0 +1,83 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Talon + * API for the Talon static site management system + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +/** + * + * @export + * @interface InfoStats + */ +export interface InfoStats { + /** + * Number of websites + * @type {number} + * @memberof InfoStats + */ + nWebsites: number; + /** + * Number of unique files + * @type {number} + * @memberof InfoStats + */ + nFiles: number; + /** + * Amount of used storage space (in bytes) + * @type {number} + * @memberof InfoStats + */ + storageUsed: number; +} + +/** + * Check if a given object implements the InfoStats interface. + */ +export function instanceOfInfoStats(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "nWebsites" in value; + isInstance = isInstance && "nFiles" in value; + isInstance = isInstance && "storageUsed" in value; + + return isInstance; +} + +export function InfoStatsFromJSON(json: any): InfoStats { + return InfoStatsFromJSONTyped(json, false); +} + +export function InfoStatsFromJSONTyped(json: any, ignoreDiscriminator: boolean): InfoStats { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'nWebsites': json['n_websites'], + 'nFiles': json['n_files'], + 'storageUsed': json['storage_used'], + }; +} + +export function InfoStatsToJSON(value?: InfoStats | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'n_websites': value.nWebsites, + 'n_files': value.nFiles, + 'storage_used': value.storageUsed, + }; +} diff --git a/ui/talon-client/src/models/InfoVersion.ts b/ui/talon-client/src/models/InfoVersion.ts new file mode 100644 index 0000000..a0521d9 --- /dev/null +++ b/ui/talon-client/src/models/InfoVersion.ts @@ -0,0 +1,110 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Talon + * API for the Talon static site management system + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +/** + * + * @export + * @interface InfoVersion + */ +export interface InfoVersion { + /** + * Talon version + * @type {string} + * @memberof InfoVersion + */ + version: string; + /** + * Commit hash + * @type {string} + * @memberof InfoVersion + */ + commit: string; + /** + * Commit date + * @type {Date} + * @memberof InfoVersion + */ + commitDate: Date; + /** + * Rust version + * @type {string} + * @memberof InfoVersion + */ + rustVersion: string; + /** + * Build target (OS and architecture) + * @type {string} + * @memberof InfoVersion + */ + buildTarget: string; + /** + * Rust build mode (`debug` / `release`) + * @type {string} + * @memberof InfoVersion + */ + buildMode: string; +} + +/** + * Check if a given object implements the InfoVersion interface. + */ +export function instanceOfInfoVersion(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "version" in value; + isInstance = isInstance && "commit" in value; + isInstance = isInstance && "commitDate" in value; + isInstance = isInstance && "rustVersion" in value; + isInstance = isInstance && "buildTarget" in value; + isInstance = isInstance && "buildMode" in value; + + return isInstance; +} + +export function InfoVersionFromJSON(json: any): InfoVersion { + return InfoVersionFromJSONTyped(json, false); +} + +export function InfoVersionFromJSONTyped(json: any, ignoreDiscriminator: boolean): InfoVersion { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'version': json['version'], + 'commit': json['commit'], + 'commitDate': (new Date(json['commit_date'])), + 'rustVersion': json['rust_version'], + 'buildTarget': json['build_target'], + 'buildMode': json['build_mode'], + }; +} + +export function InfoVersionToJSON(value?: InfoVersion | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'version': value.version, + 'commit': value.commit, + 'commit_date': (value.commitDate.toISOString()), + 'rust_version': value.rustVersion, + 'build_target': value.buildTarget, + 'build_mode': value.buildMode, + }; +} diff --git a/ui/talon-client/src/models/SourceIcon.ts b/ui/talon-client/src/models/SourceIcon.ts new file mode 100644 index 0000000..ae3a1e8 --- /dev/null +++ b/ui/talon-client/src/models/SourceIcon.ts @@ -0,0 +1,41 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Talon + * API for the Talon static site management system + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +/** + * + * @export + */ +export const SourceIcon = { + Link: 'link', + Git: 'git', + Github: 'github', + Gitlab: 'gitlab', + Gitea: 'gitea', + Bitbucket: 'bitbucket' +} as const; +export type SourceIcon = typeof SourceIcon[keyof typeof SourceIcon]; + + +export function SourceIconFromJSON(json: any): SourceIcon { + return SourceIconFromJSONTyped(json, false); +} + +export function SourceIconFromJSONTyped(json: any, ignoreDiscriminator: boolean): SourceIcon { + return json as SourceIcon; +} + +export function SourceIconToJSON(value?: SourceIcon | null): any { + return value as any; +} diff --git a/ui/talon-client/src/models/Stats.ts b/ui/talon-client/src/models/Stats.ts new file mode 100644 index 0000000..66c6e00 --- /dev/null +++ b/ui/talon-client/src/models/Stats.ts @@ -0,0 +1,83 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Talon + * API for the Talon static site management system + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +/** + * Stats about your Talon instance + * @export + * @interface Stats + */ +export interface Stats { + /** + * Number of websites + * @type {number} + * @memberof Stats + */ + nWebsites: number; + /** + * Number of unique files + * @type {number} + * @memberof Stats + */ + nFiles: number; + /** + * Amount of used storage space (in bytes) + * @type {number} + * @memberof Stats + */ + storageUsed: number; +} + +/** + * Check if a given object implements the Stats interface. + */ +export function instanceOfStats(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "nWebsites" in value; + isInstance = isInstance && "nFiles" in value; + isInstance = isInstance && "storageUsed" in value; + + return isInstance; +} + +export function StatsFromJSON(json: any): Stats { + return StatsFromJSONTyped(json, false); +} + +export function StatsFromJSONTyped(json: any, ignoreDiscriminator: boolean): Stats { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'nWebsites': json['n_websites'], + 'nFiles': json['n_files'], + 'storageUsed': json['storage_used'], + }; +} + +export function StatsToJSON(value?: Stats | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'n_websites': value.nWebsites, + 'n_files': value.nFiles, + 'storage_used': value.storageUsed, + }; +} diff --git a/ui/talon-client/src/models/Version.ts b/ui/talon-client/src/models/Version.ts new file mode 100644 index 0000000..20bdf42 --- /dev/null +++ b/ui/talon-client/src/models/Version.ts @@ -0,0 +1,86 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Talon + * API for the Talon static site management system + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +/** + * Website version + * @export + * @interface Version + */ +export interface Version { + /** + * Version ID + * @type {number} + * @memberof Version + */ + id: number; + /** + * Version creation date + * @type {Date} + * @memberof Version + */ + createdAt: Date; + /** + * Associated version data + * + * This is an arbitrary string map that can hold build information and other stuff + * and will be displayed in the site info dialog. + * @type {{ [key: string]: string; }} + * @memberof Version + */ + data: { [key: string]: string; }; +} + +/** + * Check if a given object implements the Version interface. + */ +export function instanceOfVersion(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "id" in value; + isInstance = isInstance && "createdAt" in value; + isInstance = isInstance && "data" in value; + + return isInstance; +} + +export function VersionFromJSON(json: any): Version { + return VersionFromJSONTyped(json, false); +} + +export function VersionFromJSONTyped(json: any, ignoreDiscriminator: boolean): Version { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'id': json['id'], + 'createdAt': (new Date(json['created_at'])), + 'data': json['data'], + }; +} + +export function VersionToJSON(value?: Version | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'id': value.id, + 'created_at': (value.createdAt.toISOString()), + 'data': value.data, + }; +} diff --git a/ui/talon-client/src/models/VersionFile.ts b/ui/talon-client/src/models/VersionFile.ts new file mode 100644 index 0000000..e39a9c4 --- /dev/null +++ b/ui/talon-client/src/models/VersionFile.ts @@ -0,0 +1,82 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Talon + * API for the Talon static site management system + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +/** + * Website file + * @export + * @interface VersionFile + */ +export interface VersionFile { + /** + * File path + * @type {string} + * @memberof VersionFile + */ + path: string; + /** + * File hash + * @type {string} + * @memberof VersionFile + */ + hash: string; + /** + * MIME file type + * @type {string} + * @memberof VersionFile + */ + mime?: string; +} + +/** + * Check if a given object implements the VersionFile interface. + */ +export function instanceOfVersionFile(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "path" in value; + isInstance = isInstance && "hash" in value; + + return isInstance; +} + +export function VersionFileFromJSON(json: any): VersionFile { + return VersionFileFromJSONTyped(json, false); +} + +export function VersionFileFromJSONTyped(json: any, ignoreDiscriminator: boolean): VersionFile { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'path': json['path'], + 'hash': json['hash'], + 'mime': !exists(json, 'mime') ? undefined : json['mime'], + }; +} + +export function VersionFileToJSON(value?: VersionFile | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'path': value.path, + 'hash': value.hash, + 'mime': value.mime, + }; +} diff --git a/ui/talon-client/src/models/VersionInfo.ts b/ui/talon-client/src/models/VersionInfo.ts new file mode 100644 index 0000000..f24b426 --- /dev/null +++ b/ui/talon-client/src/models/VersionInfo.ts @@ -0,0 +1,110 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Talon + * API for the Talon static site management system + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +/** + * Information about a Talon version + * @export + * @interface VersionInfo + */ +export interface VersionInfo { + /** + * Talon version + * @type {string} + * @memberof VersionInfo + */ + version: string; + /** + * Commit hash + * @type {string} + * @memberof VersionInfo + */ + commit: string; + /** + * Commit date + * @type {Date} + * @memberof VersionInfo + */ + commitDate: Date; + /** + * Rust version + * @type {string} + * @memberof VersionInfo + */ + rustVersion: string; + /** + * Build target (OS and architecture) + * @type {string} + * @memberof VersionInfo + */ + buildTarget: string; + /** + * Rust build mode (`debug` / `release`) + * @type {string} + * @memberof VersionInfo + */ + buildMode: string; +} + +/** + * Check if a given object implements the VersionInfo interface. + */ +export function instanceOfVersionInfo(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "version" in value; + isInstance = isInstance && "commit" in value; + isInstance = isInstance && "commitDate" in value; + isInstance = isInstance && "rustVersion" in value; + isInstance = isInstance && "buildTarget" in value; + isInstance = isInstance && "buildMode" in value; + + return isInstance; +} + +export function VersionInfoFromJSON(json: any): VersionInfo { + return VersionInfoFromJSONTyped(json, false); +} + +export function VersionInfoFromJSONTyped(json: any, ignoreDiscriminator: boolean): VersionInfo { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'version': json['version'], + 'commit': json['commit'], + 'commitDate': (new Date(json['commit_date'])), + 'rustVersion': json['rust_version'], + 'buildTarget': json['build_target'], + 'buildMode': json['build_mode'], + }; +} + +export function VersionInfoToJSON(value?: VersionInfo | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'version': value.version, + 'commit': value.commit, + 'commit_date': (value.commitDate.toISOString()), + 'rust_version': value.rustVersion, + 'build_target': value.buildTarget, + 'build_mode': value.buildMode, + }; +} diff --git a/ui/talon-client/src/models/Visibility.ts b/ui/talon-client/src/models/Visibility.ts new file mode 100644 index 0000000..e2dccf7 --- /dev/null +++ b/ui/talon-client/src/models/Visibility.ts @@ -0,0 +1,38 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Talon + * API for the Talon static site management system + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +/** + * + * @export + */ +export const Visibility = { + Featured: 'featured', + Searchable: 'searchable', + Hidden: 'hidden' +} as const; +export type Visibility = typeof Visibility[keyof typeof Visibility]; + + +export function VisibilityFromJSON(json: any): Visibility { + return VisibilityFromJSONTyped(json, false); +} + +export function VisibilityFromJSONTyped(json: any, ignoreDiscriminator: boolean): Visibility { + return json as Visibility; +} + +export function VisibilityToJSON(value?: Visibility | null): any { + return value as any; +} diff --git a/ui/talon-client/src/models/Website.ts b/ui/talon-client/src/models/Website.ts new file mode 100644 index 0000000..ae97648 --- /dev/null +++ b/ui/talon-client/src/models/Website.ts @@ -0,0 +1,139 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Talon + * API for the Talon static site management system + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +import type { SourceIcon } from './SourceIcon'; +import { + SourceIconFromJSON, + SourceIconFromJSONTyped, + SourceIconToJSON, +} from './SourceIcon'; +import type { Visibility } from './Visibility'; +import { + VisibilityFromJSON, + VisibilityFromJSONTyped, + VisibilityToJSON, +} from './Visibility'; + +/** + * Website + * @export + * @interface Website + */ +export interface Website { + /** + * Website subdomain + * @type {string} + * @memberof Website + */ + subdomain: string; + /** + * Website name + * @type {string} + * @memberof Website + */ + name: string; + /** + * Website creation date + * @type {Date} + * @memberof Website + */ + createdAt: Date; + /** + * Latest version ID + * @type {number} + * @memberof Website + */ + latestVersion?: number; + /** + * Color of the page icon + * + * Format: `#7935df` + * @type {string} + * @memberof Website + */ + color?: string; + /** + * + * @type {Visibility} + * @memberof Website + */ + visibility: Visibility; + /** + * Link to the source of the page + * @type {string} + * @memberof Website + */ + sourceUrl?: string; + /** + * + * @type {SourceIcon} + * @memberof Website + */ + sourceIcon?: SourceIcon; +} + +/** + * Check if a given object implements the Website interface. + */ +export function instanceOfWebsite(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "subdomain" in value; + isInstance = isInstance && "name" in value; + isInstance = isInstance && "createdAt" in value; + isInstance = isInstance && "visibility" in value; + + return isInstance; +} + +export function WebsiteFromJSON(json: any): Website { + return WebsiteFromJSONTyped(json, false); +} + +export function WebsiteFromJSONTyped(json: any, ignoreDiscriminator: boolean): Website { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'subdomain': json['subdomain'], + 'name': json['name'], + 'createdAt': (new Date(json['created_at'])), + 'latestVersion': !exists(json, 'latest_version') ? undefined : json['latest_version'], + 'color': !exists(json, 'color') ? undefined : json['color'], + 'visibility': VisibilityFromJSON(json['visibility']), + 'sourceUrl': !exists(json, 'source_url') ? undefined : json['source_url'], + 'sourceIcon': !exists(json, 'source_icon') ? undefined : SourceIconFromJSON(json['source_icon']), + }; +} + +export function WebsiteToJSON(value?: Website | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'subdomain': value.subdomain, + 'name': value.name, + 'created_at': (value.createdAt.toISOString()), + 'latest_version': value.latestVersion, + 'color': value.color, + 'visibility': VisibilityToJSON(value.visibility), + 'source_url': value.sourceUrl, + 'source_icon': SourceIconToJSON(value.sourceIcon), + }; +} diff --git a/ui/talon-client/src/models/WebsiteNew.ts b/ui/talon-client/src/models/WebsiteNew.ts new file mode 100644 index 0000000..1980feb --- /dev/null +++ b/ui/talon-client/src/models/WebsiteNew.ts @@ -0,0 +1,110 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Talon + * API for the Talon static site management system + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +import type { SourceIcon } from './SourceIcon'; +import { + SourceIconFromJSON, + SourceIconFromJSONTyped, + SourceIconToJSON, +} from './SourceIcon'; +import type { Visibility } from './Visibility'; +import { + VisibilityFromJSON, + VisibilityFromJSONTyped, + VisibilityToJSON, +} from './Visibility'; + +/** + * Create a new website + * @export + * @interface WebsiteNew + */ +export interface WebsiteNew { + /** + * Website name + * @type {string} + * @memberof WebsiteNew + */ + name: string; + /** + * Color of the page icon + * @type {string} + * @memberof WebsiteNew + */ + color?: string; + /** + * + * @type {Visibility} + * @memberof WebsiteNew + */ + visibility?: Visibility; + /** + * Link to the source of the page + * @type {string} + * @memberof WebsiteNew + */ + sourceUrl?: string; + /** + * + * @type {SourceIcon} + * @memberof WebsiteNew + */ + sourceIcon?: SourceIcon; +} + +/** + * Check if a given object implements the WebsiteNew interface. + */ +export function instanceOfWebsiteNew(value: object): boolean { + let isInstance = true; + isInstance = isInstance && "name" in value; + + return isInstance; +} + +export function WebsiteNewFromJSON(json: any): WebsiteNew { + return WebsiteNewFromJSONTyped(json, false); +} + +export function WebsiteNewFromJSONTyped(json: any, ignoreDiscriminator: boolean): WebsiteNew { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'name': json['name'], + 'color': !exists(json, 'color') ? undefined : json['color'], + 'visibility': !exists(json, 'visibility') ? undefined : VisibilityFromJSON(json['visibility']), + 'sourceUrl': !exists(json, 'source_url') ? undefined : json['source_url'], + 'sourceIcon': !exists(json, 'source_icon') ? undefined : SourceIconFromJSON(json['source_icon']), + }; +} + +export function WebsiteNewToJSON(value?: WebsiteNew | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'name': value.name, + 'color': value.color, + 'visibility': VisibilityToJSON(value.visibility), + 'source_url': value.sourceUrl, + 'source_icon': SourceIconToJSON(value.sourceIcon), + }; +} diff --git a/ui/talon-client/src/models/WebsiteUpdate.ts b/ui/talon-client/src/models/WebsiteUpdate.ts new file mode 100644 index 0000000..70c4440 --- /dev/null +++ b/ui/talon-client/src/models/WebsiteUpdate.ts @@ -0,0 +1,111 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Talon + * API for the Talon static site management system + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +import type { SourceIcon } from './SourceIcon'; +import { + SourceIconFromJSON, + SourceIconFromJSONTyped, + SourceIconToJSON, +} from './SourceIcon'; +import type { Visibility } from './Visibility'; +import { + VisibilityFromJSON, + VisibilityFromJSONTyped, + VisibilityToJSON, +} from './Visibility'; + +/** + * Update a website with the contained values + * + * Values set to `None` remain unchanged. + * @export + * @interface WebsiteUpdate + */ +export interface WebsiteUpdate { + /** + * Website name + * @type {string} + * @memberof WebsiteUpdate + */ + name?: string; + /** + * Color of the page icon + * @type {string} + * @memberof WebsiteUpdate + */ + color?: string; + /** + * + * @type {Visibility} + * @memberof WebsiteUpdate + */ + visibility?: Visibility; + /** + * Link to the source of the page + * @type {string} + * @memberof WebsiteUpdate + */ + sourceUrl?: string; + /** + * + * @type {SourceIcon} + * @memberof WebsiteUpdate + */ + sourceIcon?: SourceIcon; +} + +/** + * Check if a given object implements the WebsiteUpdate interface. + */ +export function instanceOfWebsiteUpdate(value: object): boolean { + let isInstance = true; + + return isInstance; +} + +export function WebsiteUpdateFromJSON(json: any): WebsiteUpdate { + return WebsiteUpdateFromJSONTyped(json, false); +} + +export function WebsiteUpdateFromJSONTyped(json: any, ignoreDiscriminator: boolean): WebsiteUpdate { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'name': !exists(json, 'name') ? undefined : json['name'], + 'color': !exists(json, 'color') ? undefined : json['color'], + 'visibility': !exists(json, 'visibility') ? undefined : VisibilityFromJSON(json['visibility']), + 'sourceUrl': !exists(json, 'source_url') ? undefined : json['source_url'], + 'sourceIcon': !exists(json, 'source_icon') ? undefined : SourceIconFromJSON(json['source_icon']), + }; +} + +export function WebsiteUpdateToJSON(value?: WebsiteUpdate | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'name': value.name, + 'color': value.color, + 'visibility': VisibilityToJSON(value.visibility), + 'source_url': value.sourceUrl, + 'source_icon': SourceIconToJSON(value.sourceIcon), + }; +} diff --git a/ui/talon-client/src/models/index.ts b/ui/talon-client/src/models/index.ts new file mode 100644 index 0000000..fca2aaa --- /dev/null +++ b/ui/talon-client/src/models/index.ts @@ -0,0 +1,14 @@ +/* tslint:disable */ +/* eslint-disable */ +export * from './Info'; +export * from './InfoStats'; +export * from './InfoVersion'; +export * from './SourceIcon'; +export * from './Stats'; +export * from './Version'; +export * from './VersionFile'; +export * from './VersionInfo'; +export * from './Visibility'; +export * from './Website'; +export * from './WebsiteNew'; +export * from './WebsiteUpdate'; diff --git a/ui/talon-client/src/runtime.ts b/ui/talon-client/src/runtime.ts new file mode 100644 index 0000000..9cb415c --- /dev/null +++ b/ui/talon-client/src/runtime.ts @@ -0,0 +1,407 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Talon + * API for the Talon static site management system + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export const BASE_PATH = "http://localhost".replace(/\/+$/, ""); + +export interface ConfigurationParameters { + basePath?: string; // override base path + fetchApi?: FetchAPI; // override for fetch implementation + middleware?: Middleware[]; // middleware to apply before/after fetch requests + queryParamsStringify?: (params: HTTPQuery) => string; // stringify function for query strings + username?: string; // parameter for basic security + password?: string; // parameter for basic security + apiKey?: string | ((name: string) => string); // parameter for apiKey security + accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string | Promise); // parameter for oauth2 security + headers?: HTTPHeaders; //header params we want to use on every request + credentials?: RequestCredentials; //value for the credentials param we want to use on each request +} + +export class Configuration { + constructor(private configuration: ConfigurationParameters = {}) {} + + set config(configuration: Configuration) { + this.configuration = configuration; + } + + get basePath(): string { + return this.configuration.basePath != null ? this.configuration.basePath : BASE_PATH; + } + + get fetchApi(): FetchAPI | undefined { + return this.configuration.fetchApi; + } + + get middleware(): Middleware[] { + return this.configuration.middleware || []; + } + + get queryParamsStringify(): (params: HTTPQuery) => string { + return this.configuration.queryParamsStringify || querystring; + } + + get username(): string | undefined { + return this.configuration.username; + } + + get password(): string | undefined { + return this.configuration.password; + } + + get apiKey(): ((name: string) => string) | undefined { + const apiKey = this.configuration.apiKey; + if (apiKey) { + return typeof apiKey === 'function' ? apiKey : () => apiKey; + } + return undefined; + } + + get accessToken(): ((name?: string, scopes?: string[]) => string | Promise) | undefined { + const accessToken = this.configuration.accessToken; + if (accessToken) { + return typeof accessToken === 'function' ? accessToken : async () => accessToken; + } + return undefined; + } + + get headers(): HTTPHeaders | undefined { + return this.configuration.headers; + } + + get credentials(): RequestCredentials | undefined { + return this.configuration.credentials; + } +} + +export const DefaultConfig = new Configuration(); + +/** + * This is the base class for all generated API classes. + */ +export class BaseAPI { + + private middleware: Middleware[]; + + constructor(protected configuration = DefaultConfig) { + this.middleware = configuration.middleware; + } + + withMiddleware(this: T, ...middlewares: Middleware[]) { + const next = this.clone(); + next.middleware = next.middleware.concat(...middlewares); + return next; + } + + withPreMiddleware(this: T, ...preMiddlewares: Array) { + const middlewares = preMiddlewares.map((pre) => ({ pre })); + return this.withMiddleware(...middlewares); + } + + withPostMiddleware(this: T, ...postMiddlewares: Array) { + const middlewares = postMiddlewares.map((post) => ({ post })); + return this.withMiddleware(...middlewares); + } + + protected async request(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction): Promise { + const { url, init } = await this.createFetchParams(context, initOverrides); + const response = await this.fetchApi(url, init); + if (response && (response.status >= 200 && response.status < 300)) { + return response; + } + throw new ResponseError(response, 'Response returned an error code'); + } + + private async createFetchParams(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction) { + let url = this.configuration.basePath + context.path; + if (context.query !== undefined && Object.keys(context.query).length !== 0) { + // only add the querystring to the URL if there are query parameters. + // this is done to avoid urls ending with a "?" character which buggy webservers + // do not handle correctly sometimes. + url += '?' + this.configuration.queryParamsStringify(context.query); + } + + const headers = Object.assign({}, this.configuration.headers, context.headers); + Object.keys(headers).forEach(key => headers[key] === undefined ? delete headers[key] : {}); + + const initOverrideFn = + typeof initOverrides === "function" + ? initOverrides + : async () => initOverrides; + + const initParams = { + method: context.method, + headers, + body: context.body, + credentials: this.configuration.credentials, + }; + + const overriddenInit: RequestInit = { + ...initParams, + ...(await initOverrideFn({ + init: initParams, + context, + })) + }; + + const init: RequestInit = { + ...overriddenInit, + body: + isFormData(overriddenInit.body) || + overriddenInit.body instanceof URLSearchParams || + isBlob(overriddenInit.body) + ? overriddenInit.body + : JSON.stringify(overriddenInit.body), + }; + + return { url, init }; + } + + private fetchApi = async (url: string, init: RequestInit) => { + let fetchParams = { url, init }; + for (const middleware of this.middleware) { + if (middleware.pre) { + fetchParams = await middleware.pre({ + fetch: this.fetchApi, + ...fetchParams, + }) || fetchParams; + } + } + let response: Response | undefined = undefined; + try { + response = await (this.configuration.fetchApi || fetch)(fetchParams.url, fetchParams.init); + } catch (e) { + for (const middleware of this.middleware) { + if (middleware.onError) { + response = await middleware.onError({ + fetch: this.fetchApi, + url: fetchParams.url, + init: fetchParams.init, + error: e, + response: response ? response.clone() : undefined, + }) || response; + } + } + if (response === undefined) { + if (e instanceof Error) { + throw new FetchError(e, 'The request failed and the interceptors did not return an alternative response'); + } else { + throw e; + } + } + } + for (const middleware of this.middleware) { + if (middleware.post) { + response = await middleware.post({ + fetch: this.fetchApi, + url: fetchParams.url, + init: fetchParams.init, + response: response.clone(), + }) || response; + } + } + return response; + } + + /** + * Create a shallow clone of `this` by constructing a new instance + * and then shallow cloning data members. + */ + private clone(this: T): T { + const constructor = this.constructor as any; + const next = new constructor(this.configuration); + next.middleware = this.middleware.slice(); + return next; + } +}; + +function isBlob(value: any): value is Blob { + return typeof Blob !== 'undefined' && value instanceof Blob; +} + +function isFormData(value: any): value is FormData { + return typeof FormData !== "undefined" && value instanceof FormData; +} + +export class ResponseError extends Error { + override name: "ResponseError" = "ResponseError"; + constructor(public response: Response, msg?: string) { + super(msg); + } +} + +export class FetchError extends Error { + override name: "FetchError" = "FetchError"; + constructor(public cause: Error, msg?: string) { + super(msg); + } +} + +export class RequiredError extends Error { + override name: "RequiredError" = "RequiredError"; + constructor(public field: string, msg?: string) { + super(msg); + } +} + +export const COLLECTION_FORMATS = { + csv: ",", + ssv: " ", + tsv: "\t", + pipes: "|", +}; + +export type FetchAPI = WindowOrWorkerGlobalScope['fetch']; + +export type Json = any; +export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'; +export type HTTPHeaders = { [key: string]: string }; +export type HTTPQuery = { [key: string]: string | number | null | boolean | Array | Set | HTTPQuery }; +export type HTTPBody = Json | FormData | URLSearchParams; +export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody }; +export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'; + +export type InitOverrideFunction = (requestContext: { init: HTTPRequestInit, context: RequestOpts }) => Promise + +export interface FetchParams { + url: string; + init: RequestInit; +} + +export interface RequestOpts { + path: string; + method: HTTPMethod; + headers: HTTPHeaders; + query?: HTTPQuery; + body?: HTTPBody; +} + +export function exists(json: any, key: string) { + const value = json[key]; + return value !== null && value !== undefined; +} + +export function querystring(params: HTTPQuery, prefix: string = ''): string { + return Object.keys(params) + .map(key => querystringSingleKey(key, params[key], prefix)) + .filter(part => part.length > 0) + .join('&'); +} + +function querystringSingleKey(key: string, value: string | number | null | undefined | boolean | Array | Set | HTTPQuery, keyPrefix: string = ''): string { + const fullKey = keyPrefix + (keyPrefix.length ? `[${key}]` : key); + if (value instanceof Array) { + const multiValue = value.map(singleValue => encodeURIComponent(String(singleValue))) + .join(`&${encodeURIComponent(fullKey)}=`); + return `${encodeURIComponent(fullKey)}=${multiValue}`; + } + if (value instanceof Set) { + const valueAsArray = Array.from(value); + return querystringSingleKey(key, valueAsArray, keyPrefix); + } + if (value instanceof Date) { + return `${encodeURIComponent(fullKey)}=${encodeURIComponent(value.toISOString())}`; + } + if (value instanceof Object) { + return querystring(value as HTTPQuery, fullKey); + } + return `${encodeURIComponent(fullKey)}=${encodeURIComponent(String(value))}`; +} + +export function mapValues(data: any, fn: (item: any) => any) { + return Object.keys(data).reduce( + (acc, key) => ({ ...acc, [key]: fn(data[key]) }), + {} + ); +} + +export function canConsumeForm(consumes: Consume[]): boolean { + for (const consume of consumes) { + if ('multipart/form-data' === consume.contentType) { + return true; + } + } + return false; +} + +export interface Consume { + contentType: string; +} + +export interface RequestContext { + fetch: FetchAPI; + url: string; + init: RequestInit; +} + +export interface ResponseContext { + fetch: FetchAPI; + url: string; + init: RequestInit; + response: Response; +} + +export interface ErrorContext { + fetch: FetchAPI; + url: string; + init: RequestInit; + error: unknown; + response?: Response; +} + +export interface Middleware { + pre?(context: RequestContext): Promise; + post?(context: ResponseContext): Promise; + onError?(context: ErrorContext): Promise; +} + +export interface ApiResponse { + raw: Response; + value(): Promise; +} + +export interface ResponseTransformer { + (json: any): T; +} + +export class JSONApiResponse { + constructor(public raw: Response, private transformer: ResponseTransformer = (jsonValue: any) => jsonValue) {} + + async value(): Promise { + return this.transformer(await this.raw.json()); + } +} + +export class VoidApiResponse { + constructor(public raw: Response) {} + + async value(): Promise { + return undefined; + } +} + +export class BlobApiResponse { + constructor(public raw: Response) {} + + async value(): Promise { + return await this.raw.blob(); + }; +} + +export class TextApiResponse { + constructor(public raw: Response) {} + + async value(): Promise { + return await this.raw.text(); + }; +} diff --git a/ui/talon-client/tsconfig.json b/ui/talon-client/tsconfig.json new file mode 100644 index 0000000..4567ec1 --- /dev/null +++ b/ui/talon-client/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "declaration": true, + "target": "es5", + "module": "commonjs", + "moduleResolution": "node", + "outDir": "dist", + "lib": [ + "es6", + "dom" + ], + "typeRoots": [ + "node_modules/@types" + ] + }, + "exclude": [ + "dist", + "node_modules" + ] +} From 7f055c98bec70cb6873a6b580af3919f9e78e1af Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Tue, 28 Mar 2023 01:13:47 +0200 Subject: [PATCH 2/2] feat: embedded UI --- Cargo.lock | 39 +++++ Cargo.toml | 1 + Justfile | 14 ++ src/assets.rs | 69 +++++++++ src/lib.rs | 1 + src/model.rs | 8 + src/server.rs | 11 +- src/storage.rs | 27 +++- tests/tests.rs | 2 +- ui/menu/package-lock.json | 10 ++ ui/menu/package.json | 1 + ui/menu/rollup.config.js | 265 +++++++++++++++++----------------- ui/menu/src/App.svelte | 12 +- ui/menu/src/util/functions.ts | 7 +- 14 files changed, 321 insertions(+), 146 deletions(-) create mode 100644 src/assets.rs diff --git a/Cargo.lock b/Cargo.lock index 558caa7..950dd3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1776,6 +1776,44 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "rust-embed" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b68543d5527e158213414a92832d2aab11a84d2571a5eb021ebe22c43aab066" +dependencies = [ + "hex", + "mime_guess", + "poem", + "rust-embed-impl", + "rust-embed-utils", + "tokio", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "6.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4e0f0ced47ded9a68374ac145edd65a6c1fa13a96447b873660b2a568a0fd7" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "7.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512b0ab6853f7e14e3c8754acb43d6f748bb9ced66aa5915a6553ac8213f7731" +dependencies = [ + "sha2", + "walkdir", +] + [[package]] name = "rustc_version" version = "0.4.0" @@ -2051,6 +2089,7 @@ dependencies = [ "regex", "rmp-serde", "rstest", + "rust-embed", "serde", "serde_json", "sha2", diff --git a/Cargo.toml b/Cargo.toml index 8dd984c..dd54876 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,7 @@ async-compression = { version = "0.3.15", features = [ clap = { version = "4.1.8", features = ["derive"] } shadow-rs = "0.21.0" walkdir = "2.3.2" +rust-embed = { version = "6.6.1", features = ["poem-ex"] } [dev-dependencies] rstest = "0.16.0" diff --git a/Justfile b/Justfile index d401299..7d24341 100644 --- a/Justfile +++ b/Justfile @@ -7,8 +7,22 @@ lint: test: cargo test --workspace +# Run the Talon server +run: + cargo run -- run -d tmp + +# Generate the openapi.json documentation oai-doc: cargo run --bin openapi +# Generate the JS API client oai-client: openapi-generator-cli generate -i openapi.json -g typescript-fetch -o ui/talon-client -p "npmName=talon-client" + +# Start the dev server for the sidebar menu +menu-dev: + cd ui/menu && npm run dev + +# Build the sidebar menu -> ui/menu/dist/talon.js +menu-build: + cd ui/menu && npm run build diff --git a/src/assets.rs b/src/assets.rs new file mode 100644 index 0000000..b682c22 --- /dev/null +++ b/src/assets.rs @@ -0,0 +1,69 @@ +use std::marker::PhantomData; + +use crate::{storage::CompressionAlg, util}; +use poem::{ + handler, + http::{header, Method, StatusCode}, + web::Path, + Request, Response, Result, +}; + +#[derive(rust_embed::RustEmbed)] +#[folder = "ui/menu/dist/"] +struct MenuAsset; + +#[handler] +pub fn menu_assets(request: &Request, path: Path) -> Result { + assets(request, path, PhantomData::) +} + +fn assets( + request: &Request, + path: Path, + _: PhantomData, +) -> Result { + if request.method() != Method::GET { + return Ok(StatusCode::METHOD_NOT_ALLOWED.into()); + } + let path = path.0; + + match A::get(&path) { + Some(content) => { + let hash = hex::encode(content.metadata.sha256_hash()); + + // if etag is matched, return 304 + if request + .headers() + .get(header::IF_NONE_MATCH) + .map(|etag| etag.to_str().unwrap_or("000000").eq(&hash)) + .unwrap_or(false) + { + return Ok(StatusCode::NOT_MODIFIED.into()); + } + + let alg = util::parse_accept_encoding(request.headers(), &[CompressionAlg::Brotli]); + let (body, body_alg) = if let Some(CompressionAlg::Brotli) = alg { + let path_compressed = format!("{path}.br"); + if let Some(compressed_content) = MenuAsset::get(&path_compressed) { + (compressed_content.data, CompressionAlg::Brotli) + } else { + (content.data, CompressionAlg::None) + } + } else { + (content.data, CompressionAlg::None) + }; + + // otherwise, return 200 with etag hash + let mime = mime_guess::from_path(path).first_or_octet_stream(); + let mut builder = Response::builder() + .header(header::CONTENT_TYPE, mime.as_ref()) + .header(header::ETAG, hash); + if let Some(encoding) = body_alg.encoding() { + builder = builder.header(header::CONTENT_ENCODING, encoding); + } + + Ok(builder.body(body.to_vec())) + } + None => Ok(Response::builder().status(StatusCode::NOT_FOUND).finish()), + } +} diff --git a/src/lib.rs b/src/lib.rs index 89530f5..f697374 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ #![warn(clippy::todo, clippy::dbg_macro)] pub mod api; +pub mod assets; pub mod config; pub mod db; pub mod model; diff --git a/src/model.rs b/src/model.rs index 45f4974..7f9f4c9 100644 --- a/src/model.rs +++ b/src/model.rs @@ -131,6 +131,14 @@ pub struct Stats { pub storage_used: u64, } +/// Embedded site config +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TalonConfig<'a> { + pub api: &'a str, + pub version: &'a str, + pub root_domain: &'a str, +} + #[derive( Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Enum, Serialize, Deserialize, )] diff --git a/src/server.rs b/src/server.rs index e62483c..3cba26a 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,6 +1,7 @@ use std::{ops::Deref, path::Path, sync::Arc}; use crate::{ + assets, config::Config, db::Db, model::{Info, VersionInfo}, @@ -82,14 +83,18 @@ impl Talon { "/", poem::endpoint::make_sync(|_| "Hello World, I am Talon"), ) - .nest("/api", api_service) + .nest( + "/api", + api_service + .with(middleware::Cors::new()) + .with(crate::middleware::LastModified), + ) .nest("/api/swagger", swagger_ui) .at( "/api/spec", poem::endpoint::make_sync(move |_| spec.clone()), ) - .with(middleware::Cors::new()) - .with(crate::middleware::LastModified); + .at("/assets/menu/*path", assets::menu_assets); let internal_domain = format!( "{}.{}", diff --git a/src/storage.rs b/src/storage.rs index ceedd4b..3449224 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -24,7 +24,7 @@ use zip::ZipArchive; use crate::{ config::Config, db::{Db, DbError}, - model::Stats, + model::{Stats, TalonConfig}, util, }; @@ -32,6 +32,7 @@ pub struct Storage { path: PathBuf, db: Db, cfg: Config, + to_inject: String, } #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -44,7 +45,7 @@ pub enum CompressionAlg { impl CompressionAlg { /// Get value of the http encoding header - fn encoding(&self) -> Option<&'static str> { + pub fn encoding(&self) -> Option<&'static str> { match self { CompressionAlg::None => None, CompressionAlg::Gzip => Some("gzip"), @@ -117,10 +118,28 @@ const TMPDIR_PREFIX: &str = "talon"; impl Storage { /// Create a new file storage using the root folder and the database pub fn new>(path: P, db: Db, cfg: Config) -> Self { + // Build the string to inject into html pages + let talon_cfg = TalonConfig { + api: &format!("{}/api", cfg.server.internal_url), + version: crate::build::PKG_VERSION, + root_domain: &cfg.server.root_domain, + }; + + let to_inject = format!( + r#" + + + +"#, + serde_json::to_string(&talon_cfg).unwrap_or_default(), + cfg.server.internal_url + ); + Self { path: path.into(), db, cfg, + to_inject, } } @@ -472,15 +491,13 @@ impl Storage { // HTML files are not precompressed and need to have UI code injected if is_html { // Inject UI code into HTML - let to_inject = "\n"; - let mut html = String::with_capacity(metadata.len() as usize); tokio::fs::File::from_std(file) .read_to_string(&mut html) .await?; if let Some(ctag_pos) = html.rfind("") { - html.insert_str(ctag_pos, to_inject); + html.insert_str(ctag_pos, &self.to_inject); } // Compress response if possible diff --git a/tests/tests.rs b/tests/tests.rs index 53f4eae..39c1420 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -426,7 +426,7 @@ mod storage { let resp = tokio_test::block_on(tln.storage.file_to_response(gf, &HeaderMap::new(), true)) .unwrap(); let body = tokio_test::block_on(resp.into_body().into_string()).unwrap(); - assert!(body.contains("\n")); + assert!(body.contains("\n")); } #[rstest] diff --git a/ui/menu/package-lock.json b/ui/menu/package-lock.json index 621c4be..347fc06 100644 --- a/ui/menu/package-lock.json +++ b/ui/menu/package-lock.json @@ -30,6 +30,7 @@ "prettier": "^2.2.1", "prettier-plugin-svelte": "^1.2.0", "rollup": "^2.57.0", + "rollup-plugin-brotli": "^3.1.0", "rollup-plugin-copy": "^3.4.0", "rollup-plugin-html-minifier": "^2.0.0", "rollup-plugin-livereload": "^2.0.5", @@ -4539,6 +4540,15 @@ "fsevents": "~2.3.2" } }, + "node_modules/rollup-plugin-brotli": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-brotli/-/rollup-plugin-brotli-3.1.0.tgz", + "integrity": "sha512-vXRPVd9B1x+aaXeBdmLKNNsai9AH3o0Qikf4u0m1icKqgi3qVA4UhOfwGaPYoAHML1GLMUnR//PDhiMHXN/M6g==", + "dev": true, + "engines": { + "node": ">=11.7.0" + } + }, "node_modules/rollup-plugin-copy": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/rollup-plugin-copy/-/rollup-plugin-copy-3.4.0.tgz", diff --git a/ui/menu/package.json b/ui/menu/package.json index 3abb221..57d7b51 100644 --- a/ui/menu/package.json +++ b/ui/menu/package.json @@ -32,6 +32,7 @@ "prettier": "^2.2.1", "prettier-plugin-svelte": "^1.2.0", "rollup": "^2.57.0", + "rollup-plugin-brotli": "^3.1.0", "rollup-plugin-copy": "^3.4.0", "rollup-plugin-html-minifier": "^2.0.0", "rollup-plugin-livereload": "^2.0.5", diff --git a/ui/menu/rollup.config.js b/ui/menu/rollup.config.js index 8b8bba3..7d689cd 100644 --- a/ui/menu/rollup.config.js +++ b/ui/menu/rollup.config.js @@ -4,154 +4,157 @@ Copyright (c) 2021 stefanonepa, MIT License https://github.com/stefanonepa/svelte-component-ts */ -import svelte from "rollup-plugin-svelte"; -import commonjs from "@rollup/plugin-commonjs"; -import resolve from "@rollup/plugin-node-resolve"; -import livereload from "rollup-plugin-livereload"; -import { terser } from "rollup-plugin-terser"; -import sveltePreprocess from "svelte-preprocess"; -import typescript from "@rollup/plugin-typescript"; -import replace from "@rollup/plugin-replace"; -import babel from "@rollup/plugin-babel"; +import svelte from "rollup-plugin-svelte" +import commonjs from "@rollup/plugin-commonjs" +import resolve from "@rollup/plugin-node-resolve" +import livereload from "rollup-plugin-livereload" +import {terser} from "rollup-plugin-terser" +import sveltePreprocess from "svelte-preprocess" +import typescript from "@rollup/plugin-typescript" +import replace from "@rollup/plugin-replace" +import babel from "@rollup/plugin-babel" +import brotli from "rollup-plugin-brotli" -import css from ".rollup/css-only"; -import { serve } from ".rollup/serve"; -import includeSass from ".rollup/includeSass"; -import htmlMinifier from ".rollup/htmlMinifier"; +import css from ".rollup/css-only" +import {serve} from ".rollup/serve" +import includeSass from ".rollup/includeSass" +import htmlMinifier from ".rollup/htmlMinifier" -const production = !process.env.ROLLUP_WATCH; -const version = process.env.VERSION || "INDEV"; +const production = !process.env.ROLLUP_WATCH +const version = process.env.VERSION || "INDEV" -const bundleName = "talon"; -const bundleFile = `${bundleName}.js`; -const bundleDir = production ? "dist" : "public"; +const bundleName = "talon" +const bundleFile = `${bundleName}.js` +const bundleDir = production ? "dist" : "public" -const appFile = "src/App.svelte"; -const styleFile = "src/style/main.sass"; -const extensions = [".svelte", ".ts", ".js", ".mjs"]; +const appFile = "src/App.svelte" +const styleFile = "src/style/main.sass" +const extensions = [".svelte", ".ts", ".js", ".mjs"] function includeCss(styles, bundle) { - const match = production ? `.shadowRoot.innerHTML="` : `.shadowRoot.innerHTML = "`; + const match = production ? `.shadowRoot.innerHTML="` : `.shadowRoot.innerHTML = "` - const currentBundle = bundle[bundleFile]; - currentBundle.code = currentBundle.code.replace( - match, - `${match}` - ); + const currentBundle = bundle[bundleFile] + currentBundle.code = currentBundle.code.replace( + match, + `${match}` + ) } export default { - input: "src/index.ts", - output: [ - { - sourcemap: !production, - format: "iife", - name: bundleName, - file: `${bundleDir}/${bundleFile}`, - plugins: [production && terser()], - }, - ], - plugins: [ - htmlMinifier({ - include: "*.svelte", - options: { - stripCarriageReturns: true, - trimLines: true, - trimElements: true, - normalizeWhiteSpace: true, - stripComments: false, - }, - }), + input: "src/index.ts", + output: [ + { + sourcemap: !production, + format: "iife", + name: bundleName, + file: `${bundleDir}/${bundleFile}`, + plugins: [production && terser()], + }, + ], + plugins: [ + htmlMinifier({ + include: "*.svelte", + options: { + stripCarriageReturns: true, + trimLines: true, + trimElements: true, + normalizeWhiteSpace: true, + stripComments: false, + }, + }), - svelte({ - preprocess: sveltePreprocess({ sourceMap: !production }), - compilerOptions: { - dev: !production, - customElement: true, - tag: "talon-sidebar", - preserveWhitespace: false, - }, - emitCss: false, - include: appFile, - }), + svelte({ + preprocess: sveltePreprocess({sourceMap: !production}), + compilerOptions: { + dev: !production, + customElement: true, + tag: "talon-sidebar", + preserveWhitespace: false, + }, + emitCss: false, + include: appFile, + }), - svelte({ - preprocess: sveltePreprocess({ sourceMap: !production }), - compilerOptions: { - dev: !production, - preserveWhitespace: false, - }, - emitCss: true, - exclude: appFile, - }), + svelte({ + preprocess: sveltePreprocess({sourceMap: !production}), + compilerOptions: { + dev: !production, + preserveWhitespace: false, + }, + emitCss: true, + exclude: appFile, + }), - css({ - output(styles, styleNodes, bundle) { - includeCss(styles, bundle); - }, - }), + css({ + output(styles, styleNodes, bundle) { + includeCss(styles, bundle) + }, + }), - includeSass({ - file: styleFile, - outputStyle: "compressed", - output: includeCss, - }), + includeSass({ + file: styleFile, + outputStyle: "compressed", + output: includeCss, + }), - resolve({ - browser: true, - dedupe: ["svelte"], - extensions, - }), - commonjs(), - typescript({ - sourceMap: !production, - inlineSources: !production, - }), + resolve({ + browser: true, + dedupe: ["svelte"], + extensions, + }), + commonjs(), + typescript({ + sourceMap: !production, + inlineSources: !production, + }), - !production && serve(), + !production && serve(), - !production && livereload(bundleDir), + !production && livereload(bundleDir), - // add transition into shadow dom - replace({ - ".ownerDocument": ".getRootNode()", - delimiters: ["", ""], - preventAssignment: true, - }), - replace({ - ".head.appendChild": ".appendChild", - delimiters: ["", ""], - preventAssignment: true, - }), + // add transition into shadow dom + replace({ + ".ownerDocument": ".getRootNode()", + delimiters: ["", ""], + preventAssignment: true, + }), + replace({ + ".head.appendChild": ".appendChild", + delimiters: ["", ""], + preventAssignment: true, + }), - // replace version placeholder - replace({ - __VERSION__: version, - preventAssignment: true, - }), + // replace version placeholder + replace({ + __VERSION__: version, + preventAssignment: true, + }), - babel({ - extensions, - exclude: "node_modules/**", - plugins: ["@babel/plugin-proposal-class-properties"], - presets: [ - [ - "@babel/preset-env", - { - modules: false, - targets: { - esmodules: true, - }, - }, - ], - "@babel/preset-typescript", - ], - babelHelpers: "bundled", - }), - ], - watch: { - chokidar: true, - clearScreen: false, - }, - external: ["./src/style/test.css"], -}; + babel({ + extensions, + exclude: "node_modules/**", + plugins: ["@babel/plugin-proposal-class-properties"], + presets: [ + [ + "@babel/preset-env", + { + modules: false, + targets: { + esmodules: true, + }, + }, + ], + "@babel/preset-typescript", + ], + babelHelpers: "bundled", + }), + + brotli(), + ], + watch: { + chokidar: true, + clearScreen: false, + }, + external: ["./src/style/test.css"], +} diff --git a/ui/menu/src/App.svelte b/ui/menu/src/App.svelte index 637c8ff..2395c2e 100644 --- a/ui/menu/src/App.svelte +++ b/ui/menu/src/App.svelte @@ -15,12 +15,14 @@ onMount(fetchWebsites); - const getColor = () => { + let color: String; + $: { if (currentWebsite && currentWebsite.color) { - return currentWebsite.color; + color = currentWebsite.color; + } else { + color = "#7935df"; } - return "#7935df"; - }; + } @@ -34,7 +36,7 @@ background: rgba(0, 0, 0, 0.6) -
+
{#if currentWebsite} {/if} diff --git a/ui/menu/src/util/functions.ts b/ui/menu/src/util/functions.ts index 3d0d3e2..fdda677 100644 --- a/ui/menu/src/util/functions.ts +++ b/ui/menu/src/util/functions.ts @@ -9,7 +9,12 @@ export function getSubdomain(): string { const rd_noport = talonConfig.root_domain.split(":", 1)[0] if (hn.endsWith("." + rd_noport)) { - return hn.substring(0, hn.length - rd_noport.length - 1) + const subdomain = hn.substring(0, hn.length - rd_noport.length - 1).split("--", 1)[0] + if (subdomain === "x") { + return "-" + } else { + return subdomain + } } return "-" }