Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7e2280969 | ||
|
|
439ffede18 | ||
|
|
c70eb4d13c | ||
|
|
bb8de5548d | ||
|
|
f79f51ea4c | ||
|
|
c62b65d7cd | ||
|
|
76a1b946eb | ||
|
|
d31bf3551c | ||
|
|
a705d7b476 | ||
|
|
b5dc42b654 | ||
|
|
080bacae80 | ||
|
|
fc7d787014 | ||
|
|
6da96f79e7 | ||
|
|
973ebf224b | ||
|
|
0ec2425f78 | ||
|
|
bc7d406e41 | ||
|
|
2c3e97190d | ||
|
|
8c974a49cc | ||
|
|
9db61956d3 | ||
|
|
bd250ecaee | ||
|
|
59dfff38bc | ||
|
|
7b144ae419 | ||
|
|
090e794460 | ||
|
|
75b6f0c1cf |
No files matched your search
@@ -6,7 +6,7 @@ tmp_dir = "tmp"
|
||||
bin = "./tmp/main"
|
||||
cmd = "go build -o ./tmp/main ./cmd"
|
||||
delay = 1000
|
||||
exclude_dir = ["assets", "tmp", "vendor", "testdata", "frontend", "swagger", "cmd/frontend", "cmd/swagger"]
|
||||
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
|
||||
exclude_file = []
|
||||
exclude_regex = ["_test.go"]
|
||||
exclude_unchanged = false
|
||||
|
||||
@@ -14,18 +14,12 @@ compose.yaml
|
||||
*.pprof
|
||||
main
|
||||
conf.yaml
|
||||
output.css
|
||||
tailwindcss
|
||||
.env
|
||||
|
||||
# Node.js (frontend build artifacts)
|
||||
node_modules
|
||||
package.json
|
||||
package-lock.json
|
||||
|
||||
# Test files
|
||||
# Test files and fixtures
|
||||
*_test.go
|
||||
integration_test.go
|
||||
internal/testutil
|
||||
*_dump.sql
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
@@ -1,19 +0,0 @@
|
||||
# Test Database Configuration
|
||||
DB_HOST=localhost
|
||||
DB_PORT=5433
|
||||
DB_USERNAME=testuser
|
||||
DB_PASSWORD=testpass
|
||||
DB_NAME=music_server_test
|
||||
|
||||
# Test Paths
|
||||
MUSIC_PATH=/Users/sebastian/projects/MusicServer/testMusic
|
||||
CHARACTERS_PATH=/Users/sebastian/projects/MusicServer/testCharacters
|
||||
|
||||
# Server Configuration
|
||||
PORT=8081
|
||||
LOG_LEVEL=debug
|
||||
LOG_JSON=false
|
||||
|
||||
# Session Authentication (for testing)
|
||||
# Set to disable session auth during development
|
||||
DISABLE_SESSION_AUTH=true
|
||||
@@ -0,0 +1,107 @@
|
||||
name: CI
|
||||
|
||||
# Branches and pull requests: unit tests, integration tests and a check that the image builds (amd64 only).
|
||||
# Version tags (v1.2.3): the same tests, then build amd64 + arm64 and push the image.
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
# A newer push to the same branch cancels the run that is still going.
|
||||
concurrency:
|
||||
group: ci-${{ gitea.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
- name: Generate templ components
|
||||
run: go tool templ generate
|
||||
- name: Vet
|
||||
run: go vet ./... && go vet -tags integration ./...
|
||||
- name: Unit tests
|
||||
run: go test -race ./...
|
||||
- name: Cross-compile for arm64
|
||||
run: GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -o /dev/null ./cmd
|
||||
|
||||
integration:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
# The test helper stops its own container, so the reaper container is not needed.
|
||||
TESTCONTAINERS_RYUK_DISABLED: "true"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
- name: Generate templ components
|
||||
run: go tool templ generate
|
||||
- name: Integration tests (PostgreSQL via testcontainers)
|
||||
run: go test -tags integration -race -timeout 15m ./internal/db/... ./internal/backend/...
|
||||
|
||||
# Checks that the image builds. Runs in parallel with the tests; nothing is pushed.
|
||||
docker:
|
||||
if: ${{ !startsWith(gitea.ref, 'refs/tags/') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
config-inline: |
|
||||
[registry."gitea.sanplex.xyz/sansan"]
|
||||
http = true
|
||||
insecure = true
|
||||
- name: Login to Gitea
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: gitea.sanplex.xyz
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.TOKEN }}
|
||||
- name: Build (amd64)
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
push: false
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max,ignore-error=true
|
||||
|
||||
# Only for version tags, and only when all tests have passed.
|
||||
publish:
|
||||
if: ${{ startsWith(gitea.ref, 'refs/tags/v') }}
|
||||
needs: [test, integration]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
config-inline: |
|
||||
[registry."gitea.sanplex.xyz/sansan"]
|
||||
http = true
|
||||
insecure = true
|
||||
- name: Login to Gitea
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: gitea.sanplex.xyz
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.TOKEN }}
|
||||
- name: Build and push (amd64 + arm64)
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
cache-from: type=gha
|
||||
tags: |
|
||||
gitea.sanplex.xyz/sansan/musicserver:${{ gitea.ref_name }}
|
||||
gitea.sanplex.xyz/sansan/musicserver:latest
|
||||
@@ -1,42 +0,0 @@
|
||||
|
||||
name: Build
|
||||
|
||||
#on:
|
||||
# release:
|
||||
# types: [published]
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
|
||||
jobs:
|
||||
# test:
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - run: echo "The release ${{ gitea.ref }} ${{ gitea.ref_name }} was published"
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: https://github.com/actions/checkout@v4
|
||||
- name: Set up Docker Buildx
|
||||
uses: https://github.com/docker/setup-buildx-action@v3
|
||||
with:
|
||||
config-inline: |
|
||||
[registry."gitea.sanplex.xyz/sansan"]
|
||||
http = true
|
||||
insecure = true
|
||||
- name: Login to Gitea
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: gitea.sanplex.xyz
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.TOKEN }}
|
||||
- name: Build
|
||||
uses: https://github.com/docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: false
|
||||
#tags: "gitea.sanplex.xyz/sansan/musicserver:${{gitea.ref_name}}, gitea.sanplex.xyz/sansan/musicserver:latest"
|
||||
@@ -1,41 +0,0 @@
|
||||
|
||||
name: Publish
|
||||
|
||||
#on:
|
||||
# release:
|
||||
# types: [published]
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*.*'
|
||||
|
||||
jobs:
|
||||
# test:
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - run: echo "The release ${{ gitea.ref }} ${{ gitea.ref_name }} was published"
|
||||
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: https://github.com/actions/checkout@v4
|
||||
- name: Set up Docker Buildx
|
||||
uses: https://github.com/docker/setup-buildx-action@v3
|
||||
with:
|
||||
config-inline: |
|
||||
[registry."gitea.sanplex.xyz/sansan"]
|
||||
http = true
|
||||
insecure = true
|
||||
- name: Login to Gitea
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: gitea.sanplex.xyz
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.TOKEN }}
|
||||
- name: Build and push Docker image
|
||||
uses: https://github.com/docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
tags: "gitea.sanplex.xyz/sansan/musicserver:${{gitea.ref_name}}, gitea.sanplex.xyz/sansan/musicserver:latest"
|
||||
@@ -1,47 +0,0 @@
|
||||
name: Integration Tests
|
||||
|
||||
on:
|
||||
workflow_dispatch: # Manual trigger only
|
||||
|
||||
jobs:
|
||||
integration-test:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: golang:1.25
|
||||
options: --privileged
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
env:
|
||||
POSTGRES_USER: testuser
|
||||
POSTGRES_PASSWORD: testpass
|
||||
POSTGRES_DB: music_server_test
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: go mod download
|
||||
|
||||
- name: Install testcontainers
|
||||
run: go install github.com/testcontainers/testcontainers-go@latest
|
||||
|
||||
- name: Run integration tests
|
||||
env:
|
||||
DB_HOST: postgres
|
||||
DB_PORT: 5432
|
||||
DB_USERNAME: testuser
|
||||
DB_PASSWORD: testpass
|
||||
DB_NAME: music_server_test
|
||||
MUSIC_PATH: ./testMusic
|
||||
CHARACTERS_PATH: ./testCharacters
|
||||
run: go test -v -timeout 30m ./...
|
||||
@@ -3,15 +3,17 @@ tmp
|
||||
.idea
|
||||
*templ.go
|
||||
conf.yaml
|
||||
output.css
|
||||
compose.yaml
|
||||
tailwindcss
|
||||
.env
|
||||
node_modules
|
||||
package.json
|
||||
package-lock.json
|
||||
cpu.pprof
|
||||
main
|
||||
|
||||
# Database dumps
|
||||
*_dump.sql
|
||||
|
||||
# Generated with `just openapi`
|
||||
/openapi.json
|
||||
/openapi-3.0.json
|
||||
|
||||
# Local cover images (COVERS_PATH)
|
||||
/covers/
|
||||
@@ -1,8 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module version="4">
|
||||
<component name="Go" enabled="true">
|
||||
<buildTags>
|
||||
<option name="customFlags">
|
||||
<array>
|
||||
<option value="prod" />
|
||||
</array>
|
||||
</option>
|
||||
</buildTags>
|
||||
</component>
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -1,5 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</state>
|
||||
</component>
|
||||
@@ -1,36 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="music_prod@192.168.86.100" uuid="e63caf71-2b1a-400d-9cc4-4b87b1f9b807">
|
||||
<driver-ref>postgresql</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:postgresql://192.168.86.100:5432/music_prod</jdbc-url>
|
||||
</data-source>
|
||||
<data-source source="LOCAL" name="music_test@192.168.86.100" uuid="d063e147-d7f0-48db-9f04-57a108261ef1">
|
||||
<driver-ref>postgresql</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:postgresql://192.168.86.100:5432/music_test</jdbc-url>
|
||||
</data-source>
|
||||
<data-source source="LOCAL" name="music_test@ssh.sanplex.xyz" uuid="a9cf3eb8-abc2-479b-9f27-12b4cbbfc321">
|
||||
<driver-ref>postgresql</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:postgresql://ssh.sanplex.xyz:9432/music_test</jdbc-url>
|
||||
</data-source>
|
||||
<data-source source="LOCAL" name="music_prod@ssh.sanplex.xyz" uuid="20917ace-4e9a-4273-bdb7-13ebc774cbba">
|
||||
<driver-ref>postgresql</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:postgresql://ssh.sanplex.xyz:9432/music_prod</jdbc-url>
|
||||
</data-source>
|
||||
<data-source source="LOCAL" name="music_test2@localhost" uuid="a423ab0a-55b0-42e1-8070-25d8ef34bfac">
|
||||
<driver-ref>postgresql</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:postgresql://localhost:5432/music_test2</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/MusicServer.iml" filepath="$PROJECT_DIR$/.idea/MusicServer.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,36 +1,45 @@
|
||||
# Stage 1: Build backend
|
||||
FROM golang:1.25-alpine as build_go
|
||||
RUN apk add --no-cache curl
|
||||
# The frontend is built and published separately; override to pin a version.
|
||||
ARG FRONTEND_IMAGE=gitea.sanplex.xyz/sansan/musicfrontend:latest
|
||||
|
||||
# Stage 1: Build the backend on the build machine and cross-compile for the target platform,
|
||||
# so multi-platform builds need no emulation.
|
||||
FROM --platform=$BUILDPLATFORM golang:1.26-alpine AS build
|
||||
ARG TARGETOS TARGETARCH
|
||||
WORKDIR /app
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
RUN --mount=type=cache,target=/go/pkg/mod go mod download
|
||||
COPY cmd/ ./cmd/
|
||||
COPY internal/ ./internal/
|
||||
RUN find . -name "*_test.go" -delete && \
|
||||
find . -name "integration_test.go" -delete
|
||||
RUN go install github.com/a-h/templ/cmd/templ@latest
|
||||
RUN templ generate
|
||||
RUN go build -o main cmd/main.go
|
||||
RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build \
|
||||
go tool templ generate
|
||||
RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build \
|
||||
CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH \
|
||||
go build -trimpath -ldflags="-s -w" -tags timetzdata -o main ./cmd
|
||||
|
||||
# Stage 2: Final image
|
||||
FROM golang:1.25-alpine
|
||||
# Stage 2: Frontend static files (the same for every platform)
|
||||
FROM --platform=$BUILDPLATFORM ${FRONTEND_IMAGE} AS frontend
|
||||
|
||||
# Stage 3: Final image. No RUN steps here, so no emulation is needed for other architectures.
|
||||
FROM alpine:3.22
|
||||
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
WORKDIR /app
|
||||
COPY --from=build /app/main .
|
||||
COPY --from=frontend /usr/share/nginx/html /frontend
|
||||
COPY ./songs/ ./songs/
|
||||
|
||||
# The music volume must be readable by uid 1000, and writable for the .id file cleanup during sync
|
||||
# and for soundtrack uploads (kept in .uploads until finished).
|
||||
USER 1000:1000
|
||||
EXPOSE 8080
|
||||
# Prometheus metrics (METRICS_PORT). Keep it off the public proxy.
|
||||
EXPOSE 2112
|
||||
VOLUME /sorted
|
||||
VOLUME /characters
|
||||
|
||||
COPY --from=build_go /app/main .
|
||||
COPY --from=gitea.sanplex.xyz/sansan/musicfrontend:latest /usr/share/nginx/html /frontend
|
||||
# Generate config.js with empty API_HOSTNAME (relative paths)
|
||||
RUN echo "window.__RUNTIME_CONFIG__ = { API_HOSTNAME: '' };" > /frontend/config.js
|
||||
COPY ./songs/ ./songs/
|
||||
# Required: DB_HOST, DB_PORT, DB_USERNAME, DB_PASSWORD, DB_NAME, MUSIC_PATH, CHARACTERS_PATH
|
||||
# Covers (optional): COVERS_PATH, a writable folder such as a /covers volume, turns covers on.
|
||||
# STEAMGRIDDB_API_KEY also turns on the automatic search (SteamGridDB, then AniList).
|
||||
# Uploads (optional): UPLOAD_MAX_FILE_MB (default 50) and UPLOAD_MAX_TOTAL_MB (default 2048).
|
||||
ENV PORT=8080
|
||||
|
||||
ENV PORT 8080
|
||||
ENV DB_HOST ""
|
||||
ENV DB_PORT ""
|
||||
ENV DB_USERNAME ""
|
||||
ENV DB_PASSWORD ""
|
||||
ENV DB_NAME ""
|
||||
ENV MUSIC_PATH ""
|
||||
ENV CHARACTERS_PATH ""
|
||||
|
||||
CMD ./main
|
||||
CMD ["./main"]
|
||||
@@ -1,8 +0,0 @@
|
||||
application: musicserver
|
||||
version: 2.2.0
|
||||
runtime: go115
|
||||
api_version: go1
|
||||
|
||||
handlers:
|
||||
- url: /.*
|
||||
script: _go_app
|
||||
@@ -1,224 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
|
||||
def load_openapi_spec(path: str) -> dict:
|
||||
"""Load OpenAPI spec from YAML or JSON file."""
|
||||
try:
|
||||
import yaml
|
||||
with open(path, "r") as f:
|
||||
return yaml.safe_load(f)
|
||||
except ImportError:
|
||||
# Fallback to JSON if PyYAML is not installed
|
||||
with open(path, "r") as f:
|
||||
return json.load(f)
|
||||
|
||||
def map_type(openapi_type: str) -> str:
|
||||
"""Map OpenAPI types to GDScript types."""
|
||||
type_mapping = {
|
||||
"integer": "int",
|
||||
"number": "float",
|
||||
"boolean": "bool",
|
||||
"array": "Array",
|
||||
"object": "Dictionary",
|
||||
}
|
||||
return type_mapping.get(openapi_type, "String")
|
||||
|
||||
def default_value(openapi_type: str):
|
||||
"""Return default values for GDScript types."""
|
||||
default_mapping = {
|
||||
"integer": "0",
|
||||
"number": "0.0",
|
||||
"boolean": "false",
|
||||
"array": "[]",
|
||||
"object": "{}",
|
||||
}
|
||||
return default_mapping.get(openapi_type, '""')
|
||||
|
||||
def sanitize_class_name(name: str) -> str:
|
||||
"""Convert a name to a valid GDScript class name."""
|
||||
# Replace invalid characters with underscores
|
||||
name = re.sub(r"[^a-zA-Z0-9_]", "_", name)
|
||||
# Capitalize first letter
|
||||
return name[0].upper() + name[1:] if name else "Model"
|
||||
|
||||
def generate_model_class(class_name: str, schema: dict) -> str:
|
||||
"""Generate a GDScript class for a model."""
|
||||
lines = [
|
||||
f"class_name {class_name}",
|
||||
"extends RefCounted",
|
||||
"",
|
||||
]
|
||||
|
||||
# Add properties
|
||||
properties = schema.get("properties", {})
|
||||
for prop_name, prop_schema in properties.items():
|
||||
prop_type = map_type(prop_schema.get("type", "string"))
|
||||
lines.append(f"var {prop_name}: {prop_type}")
|
||||
|
||||
# Add _init method
|
||||
lines.extend([
|
||||
"",
|
||||
"func _init(data: Dictionary):",
|
||||
])
|
||||
for prop_name in properties:
|
||||
prop_type = map_type(properties[prop_name].get("type", "string"))
|
||||
default = default_value(properties[prop_name].get("type", "string"))
|
||||
lines.append(f' {prop_name} = data.get("{prop_name}", {default})')
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def generate_api_client(path: str, method: str, endpoint: dict) -> str:
|
||||
"""Generate a GDScript API client for an endpoint."""
|
||||
# Sanitize path for class name
|
||||
class_name = sanitize_class_name(path.replace("/", "_").replace("{", "").replace("}", "")) + method.capitalize()
|
||||
# Format URL (replace {param} with %s for Godot's string formatting)
|
||||
url = path.replace("{", "%").replace("}", "s")
|
||||
full_url = f'"{BASE_URL}{url}"'
|
||||
|
||||
lines = [
|
||||
f"class_name {class_name}",
|
||||
"extends RefCounted",
|
||||
"",
|
||||
"var http_request: HTTPRequest",
|
||||
"",
|
||||
"func _init(node: Node):",
|
||||
" http_request = HTTPRequest.new()",
|
||||
" node.add_child(http_request)",
|
||||
' http_request.connect("request_completed", self, "_on_request_completed")',
|
||||
"",
|
||||
f"func call(params: Dictionary, callback: Callable):",
|
||||
f" var url := {full_url}",
|
||||
' var headers = ["User-Agent: MyGodotApp"]',
|
||||
" var error := http_request.request(url, headers)",
|
||||
" if error != OK:",
|
||||
' push_error("HTTP request failed.")',
|
||||
" return",
|
||||
" http_request.set_meta(\"callback\", callback)",
|
||||
"",
|
||||
"func _on_request_completed(result: int, response_code: int, headers: PoolStringArray, body: PoolByteArray):",
|
||||
" var callback := http_request.get_meta(\"callback\")",
|
||||
" if callback:",
|
||||
" var response_body = body.get_string_from_utf8()",
|
||||
" var json = JSON.new()",
|
||||
" if json.parse(response_body) == OK:",
|
||||
" callback.call(json.get_data())",
|
||||
" else:",
|
||||
" callback.call(null)",
|
||||
]
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def generate_tag_client(tag: str, endpoints: list) -> str:
|
||||
"""Generate a GDScript API client for all endpoints with a given tag."""
|
||||
class_name = sanitize_class_name(tag) + "API"
|
||||
|
||||
lines = [
|
||||
f"class_name {class_name}",
|
||||
"extends RefCounted",
|
||||
"",
|
||||
"var http_request: HTTPRequest",
|
||||
"var base_url: String",
|
||||
"",
|
||||
"func _init(node: Node, base_url_param: String):",
|
||||
" http_request = HTTPRequest.new()",
|
||||
" node.add_child(http_request)",
|
||||
' http_request.connect("request_completed", self, "_on_request_completed")',
|
||||
" base_url = base_url_param",
|
||||
"",
|
||||
]
|
||||
|
||||
# Generate a method for each endpoint
|
||||
for path, method, endpoint in endpoints:
|
||||
# Sanitize method name for GDScript
|
||||
method_name = method.lower()
|
||||
# Create a valid function name from the path
|
||||
func_name = "call_" + path.replace("/", "_").replace("{", "").replace("}", "").replace("-", "_")
|
||||
|
||||
# Format URL (replace {param} with %s for Godot's string formatting)
|
||||
url = path.replace("{", "%").replace("}", "s")
|
||||
|
||||
lines.extend([
|
||||
f"func {func_name}(params: Dictionary = {{}}, callback: Callable):",
|
||||
f' var url := base_url + "{url}"',
|
||||
' var headers = ["User-Agent: MyGodotApp"]',
|
||||
f" var error := http_request.request(url, headers, false, HTTPClient.METHOD_{method.upper()})",
|
||||
" if error != OK:",
|
||||
' push_error("HTTP request failed.")',
|
||||
" return",
|
||||
" http_request.set_meta(\"callback\", callback)",
|
||||
"",
|
||||
])
|
||||
|
||||
# Add the completion handler
|
||||
lines.extend([
|
||||
"func _on_request_completed(result: int, response_code: int, headers: PoolStringArray, body: PoolByteArray):",
|
||||
" var callback := http_request.get_meta(\"callback\")",
|
||||
" if callback:",
|
||||
" var response_body = body.get_string_from_utf8()",
|
||||
" var json = JSON.new()",
|
||||
" if json.parse(response_body) == OK:",
|
||||
" callback.call(json.get_data())",
|
||||
" else:",
|
||||
" callback.call(null)",
|
||||
])
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def generate_code(spec: dict, output_dir: str):
|
||||
"""Generate all GDScript files from the OpenAPI spec."""
|
||||
# Create output directory
|
||||
Path(output_dir).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Generate models
|
||||
schemas = spec.get("definitions", {}) # Swagger 2.0 uses "definitions"
|
||||
if not schemas:
|
||||
schemas = spec.get("components", {}).get("schemas", {})
|
||||
|
||||
for schema_name, schema in schemas.items():
|
||||
class_name = sanitize_class_name(schema_name)
|
||||
code = generate_model_class(class_name, schema)
|
||||
output_path = Path(output_dir) / f"{class_name}.gd"
|
||||
with open(output_path, "w") as f:
|
||||
f.write(code)
|
||||
print(f"Generated model: {output_path}")
|
||||
|
||||
# Group endpoints by tag
|
||||
paths = spec.get("paths", {})
|
||||
tag_endpoints = defaultdict(list)
|
||||
|
||||
for path, methods in paths.items():
|
||||
for method, endpoint in methods.items():
|
||||
tags = endpoint.get("tags", ["default"])
|
||||
for tag in tags:
|
||||
tag_endpoints[tag].append((path, method, endpoint))
|
||||
|
||||
# Generate one file per tag
|
||||
for tag, endpoints in tag_endpoints.items():
|
||||
code = generate_tag_client(tag, endpoints)
|
||||
class_name = sanitize_class_name(tag) + "API"
|
||||
output_path = Path(output_dir) / f"{class_name}.gd"
|
||||
with open(output_path, "w") as f:
|
||||
f.write(code)
|
||||
print(f"Generated API client for tag '{tag}': {output_path}")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Generate Godot API clients from OpenAPI spec")
|
||||
parser.add_argument("input", help="Path to the OpenAPI JSON/YAML file")
|
||||
parser.add_argument("-o", "--output", default="godot_generated", help="Output directory for GDScript files")
|
||||
args = parser.parse_args()
|
||||
|
||||
spec = load_openapi_spec(args.input)
|
||||
generate_code(spec, args.output)
|
||||
print("Done!")
|
||||
print("Note: When initializing the API classes, pass the base URL as a parameter:")
|
||||
print(" var music_api = MusicAPI.new()")
|
||||
print(" music_api._init(get_node('/root'), 'http://localhost:8080')")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,976 +0,0 @@
|
||||
definitions:
|
||||
backend.SongInfoForStats:
|
||||
properties:
|
||||
file_name:
|
||||
type: string
|
||||
path:
|
||||
type: string
|
||||
song_name:
|
||||
type: string
|
||||
soundtrack_id:
|
||||
type: integer
|
||||
soundtrack_name:
|
||||
type: string
|
||||
times_played:
|
||||
type: integer
|
||||
type: object
|
||||
backend.SoundtrackWithSongs:
|
||||
properties:
|
||||
songs:
|
||||
items:
|
||||
$ref: '#/definitions/backend.SongInfoForStats'
|
||||
type: array
|
||||
soundtrack_id:
|
||||
type: integer
|
||||
soundtrack_last_played:
|
||||
type: string
|
||||
soundtrack_name:
|
||||
type: string
|
||||
soundtrack_played:
|
||||
type: integer
|
||||
type: object
|
||||
backend.StatisticsSummary:
|
||||
properties:
|
||||
avg_soundtrack_plays:
|
||||
type: number
|
||||
max_soundtrack_plays:
|
||||
type: integer
|
||||
min_soundtrack_plays:
|
||||
type: integer
|
||||
never_played_soundtracks:
|
||||
type: integer
|
||||
played_soundtracks:
|
||||
type: integer
|
||||
total_soundtrack_plays:
|
||||
type: integer
|
||||
total_soundtracks:
|
||||
type: integer
|
||||
type: object
|
||||
backend.VersionData:
|
||||
properties:
|
||||
changelog:
|
||||
example:
|
||||
- '["Initial release"'
|
||||
- '"Bug fixes"]'
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
version:
|
||||
example: 1.0.0
|
||||
type: string
|
||||
type: object
|
||||
server.TokenRequest:
|
||||
properties:
|
||||
client_type:
|
||||
description: 'Optional: "web", "mobile", "api"'
|
||||
type: string
|
||||
password:
|
||||
description: 'Required: API password'
|
||||
type: string
|
||||
type: object
|
||||
server.TokenResponse:
|
||||
properties:
|
||||
client_type:
|
||||
type: string
|
||||
expires_at:
|
||||
type: string
|
||||
token:
|
||||
type: string
|
||||
type: object
|
||||
info:
|
||||
contact: {}
|
||||
paths:
|
||||
/api/v1/statistics/songs/least-played:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Returns the top N least played songs with their soundtrack info
|
||||
parameters:
|
||||
- description: 'Number of results (default: 10)'
|
||||
in: query
|
||||
name: limit
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/backend.SongInfoForStats'
|
||||
type: array
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
summary: Get least played songs
|
||||
tags:
|
||||
- statistics
|
||||
/api/v1/statistics/songs/most-played:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Returns the top N most played songs with their soundtrack info
|
||||
parameters:
|
||||
- description: 'Number of results (default: 10)'
|
||||
in: query
|
||||
name: limit
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/backend.SongInfoForStats'
|
||||
type: array
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
summary: Get most played songs
|
||||
tags:
|
||||
- statistics
|
||||
/api/v1/statistics/soundtracks/last-played:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Returns the most recently played soundtracks
|
||||
parameters:
|
||||
- description: 'Number of results (default: 10)'
|
||||
in: query
|
||||
name: limit
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/backend.SoundtrackWithSongs'
|
||||
type: array
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
summary: Get last played soundtracks
|
||||
tags:
|
||||
- statistics
|
||||
/api/v1/statistics/soundtracks/least-played:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Returns the top N least played soundtracks with their songs
|
||||
parameters:
|
||||
- description: 'Number of results (default: 10)'
|
||||
in: query
|
||||
name: limit
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/backend.SoundtrackWithSongs'
|
||||
type: array
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
summary: Get least played soundtracks
|
||||
tags:
|
||||
- statistics
|
||||
/api/v1/statistics/soundtracks/most-played:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Returns the top N most played soundtracks with their songs
|
||||
parameters:
|
||||
- description: 'Number of results (default: 10)'
|
||||
in: query
|
||||
name: limit
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/backend.SoundtrackWithSongs'
|
||||
type: array
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
summary: Get most played soundtracks
|
||||
tags:
|
||||
- statistics
|
||||
/api/v1/statistics/soundtracks/never-played:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Returns all soundtracks that have never been played (times_played
|
||||
= 0)
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/backend.SoundtrackWithSongs'
|
||||
type: array
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
summary: Get never played soundtracks
|
||||
tags:
|
||||
- statistics
|
||||
/api/v1/statistics/soundtracks/oldest-played:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Returns the least recently played soundtracks (that have been played
|
||||
at least once)
|
||||
parameters:
|
||||
- description: 'Number of results (default: 10)'
|
||||
in: query
|
||||
name: limit
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/backend.SoundtrackWithSongs'
|
||||
type: array
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
summary: Get oldest played soundtracks
|
||||
tags:
|
||||
- statistics
|
||||
/api/v1/statistics/summary:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Returns overall statistics about the music library
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/backend.StatisticsSummary'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
summary: Get statistics summary
|
||||
tags:
|
||||
- statistics
|
||||
/api/v1/token:
|
||||
delete:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Deletes the current session token
|
||||
parameters:
|
||||
- description: Bearer token
|
||||
in: header
|
||||
name: Authorization
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
summary: Invalidate session token
|
||||
tags:
|
||||
- auth
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Returns a new session token for API access. Requires valid API
|
||||
password.
|
||||
parameters:
|
||||
- description: Password and client type
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/server.TokenRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/server.TokenResponse'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
summary: Create session token
|
||||
tags:
|
||||
- auth
|
||||
/api/v1/token/cleanup:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Removes all expired session tokens from the database
|
||||
parameters:
|
||||
- description: Bearer token
|
||||
in: header
|
||||
name: Authorization
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
summary: Cleanup expired sessions
|
||||
tags:
|
||||
- auth
|
||||
/character:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Returns the image for a specific character
|
||||
parameters:
|
||||
- description: Character name
|
||||
in: query
|
||||
name: name
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- image/png
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: file
|
||||
summary: Get character image
|
||||
tags:
|
||||
- characters
|
||||
/characters:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Returns a list of all available characters
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
summary: Get list of characters
|
||||
tags:
|
||||
- characters
|
||||
/download:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Checks for the latest version of the application
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
summary: Check for latest version
|
||||
tags:
|
||||
- download
|
||||
/download/linux:
|
||||
get:
|
||||
description: Redirects to download the latest Linux version
|
||||
produces:
|
||||
- application/octet-stream
|
||||
responses:
|
||||
"302":
|
||||
description: Found
|
||||
schema:
|
||||
type: string
|
||||
summary: Download latest Linux version
|
||||
tags:
|
||||
- download
|
||||
/download/list:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Lists all assets available for the latest version
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
summary: List assets of latest version
|
||||
tags:
|
||||
- download
|
||||
/download/windows:
|
||||
get:
|
||||
description: Redirects to download the latest Windows version
|
||||
produces:
|
||||
- application/octet-stream
|
||||
responses:
|
||||
"302":
|
||||
description: Found
|
||||
schema:
|
||||
type: string
|
||||
summary: Download latest Windows version
|
||||
tags:
|
||||
- download
|
||||
/health:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Returns the health status of the server
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
summary: Check server health
|
||||
tags:
|
||||
- health
|
||||
/music:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Returns a specific song by name
|
||||
parameters:
|
||||
- description: Song name
|
||||
in: query
|
||||
name: song
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- audio/mpeg
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: file
|
||||
"400":
|
||||
description: song can't be empty
|
||||
schema:
|
||||
type: string
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
type: string
|
||||
"423":
|
||||
description: Syncing is in progress
|
||||
schema:
|
||||
type: string
|
||||
summary: Get a specific song
|
||||
tags:
|
||||
- music
|
||||
/music/addPlayed:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Adds the latest song to the played list
|
||||
responses:
|
||||
"204":
|
||||
description: ""
|
||||
"423":
|
||||
description: Syncing is in progress
|
||||
schema:
|
||||
type: string
|
||||
summary: Add latest to played
|
||||
tags:
|
||||
- music
|
||||
/music/addQue:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Adds the latest song to the queue
|
||||
responses:
|
||||
"204":
|
||||
description: ""
|
||||
"423":
|
||||
description: Syncing is in progress
|
||||
schema:
|
||||
type: string
|
||||
summary: Add latest to queue
|
||||
tags:
|
||||
- music
|
||||
/music/all/order:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Returns a list of all soundtracks in order
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
type: array
|
||||
"423":
|
||||
description: Syncing is in progress
|
||||
schema:
|
||||
type: string
|
||||
summary: Get all soundtracks
|
||||
tags:
|
||||
- music
|
||||
/music/all/random:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Returns a list of all soundtracks in random order
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
type: array
|
||||
"423":
|
||||
description: Syncing is in progress
|
||||
schema:
|
||||
type: string
|
||||
summary: Get all soundtracks random
|
||||
tags:
|
||||
- music
|
||||
/music/info:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Returns information about the current song
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
summary: Get current song info
|
||||
tags:
|
||||
- music
|
||||
/music/list:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Returns a list of played songs
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
type: array
|
||||
summary: Get played songs list
|
||||
tags:
|
||||
- music
|
||||
/music/next:
|
||||
get:
|
||||
description: Returns the next song in the queue
|
||||
produces:
|
||||
- audio/mpeg
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: file
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
type: string
|
||||
"423":
|
||||
description: Syncing is in progress
|
||||
schema:
|
||||
type: string
|
||||
summary: Get next song
|
||||
tags:
|
||||
- music
|
||||
/music/played:
|
||||
put:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Marks a song as played by its ID
|
||||
parameters:
|
||||
- description: Song ID
|
||||
in: query
|
||||
name: song
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"204":
|
||||
description: ""
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
type: string
|
||||
"423":
|
||||
description: Syncing is in progress
|
||||
schema:
|
||||
type: string
|
||||
summary: Mark song as played
|
||||
tags:
|
||||
- music
|
||||
/music/previous:
|
||||
get:
|
||||
description: Returns the previous song in the queue
|
||||
produces:
|
||||
- audio/mpeg
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: file
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
type: string
|
||||
"423":
|
||||
description: Syncing is in progress
|
||||
schema:
|
||||
type: string
|
||||
summary: Get previous song
|
||||
tags:
|
||||
- music
|
||||
/music/rand:
|
||||
get:
|
||||
description: Returns a random song
|
||||
produces:
|
||||
- audio/mpeg
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: file
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
type: string
|
||||
"423":
|
||||
description: Syncing is in progress
|
||||
schema:
|
||||
type: string
|
||||
summary: Get random song
|
||||
tags:
|
||||
- music
|
||||
/music/rand/classic:
|
||||
get:
|
||||
description: Returns a random song from the classic selection
|
||||
produces:
|
||||
- audio/mpeg
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: file
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
type: string
|
||||
"423":
|
||||
description: Syncing is in progress
|
||||
schema:
|
||||
type: string
|
||||
summary: Get random classic song
|
||||
tags:
|
||||
- music
|
||||
/music/rand/low:
|
||||
get:
|
||||
description: Returns a random song with low chance selection
|
||||
produces:
|
||||
- audio/mpeg
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: file
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
type: string
|
||||
"423":
|
||||
description: Syncing is in progress
|
||||
schema:
|
||||
type: string
|
||||
summary: Get random song with low chance
|
||||
tags:
|
||||
- music
|
||||
/music/reset:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Resets the music state
|
||||
responses:
|
||||
"204":
|
||||
description: ""
|
||||
"423":
|
||||
description: Syncing is in progress
|
||||
schema:
|
||||
type: string
|
||||
summary: Reset music state
|
||||
tags:
|
||||
- music
|
||||
/music/soundTest:
|
||||
get:
|
||||
description: Returns the sound check song
|
||||
produces:
|
||||
- audio/mpeg
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: file
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
type: string
|
||||
"423":
|
||||
description: Syncing is in progress
|
||||
schema:
|
||||
type: string
|
||||
summary: Get sound check song
|
||||
tags:
|
||||
- music
|
||||
/sync:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Starts syncing soundtracks with only new changes
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Start syncing soundtracks
|
||||
schema:
|
||||
type: string
|
||||
"423":
|
||||
description: Syncing is in progress
|
||||
schema:
|
||||
type: string
|
||||
summary: Sync soundtracks with only changes
|
||||
tags:
|
||||
- sync
|
||||
/sync/full:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Starts a full sync of all soundtracks
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Start syncing soundtracks full
|
||||
schema:
|
||||
type: string
|
||||
"423":
|
||||
description: Syncing is in progress
|
||||
schema:
|
||||
type: string
|
||||
summary: Sync all soundtracks fully
|
||||
tags:
|
||||
- sync
|
||||
/sync/progress:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Returns the current sync progress or result
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
summary: Get sync progress
|
||||
tags:
|
||||
- sync
|
||||
/sync/reset:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Resets the soundtracks database by deleting all soundtracks and
|
||||
songs
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Soundtracks and songs are deleted from the database
|
||||
schema:
|
||||
type: string
|
||||
"423":
|
||||
description: Syncing is in progress
|
||||
schema:
|
||||
type: string
|
||||
summary: Reset soundtracks database
|
||||
tags:
|
||||
- sync
|
||||
/version:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: get latest version info
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/backend.VersionData'
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
type: string
|
||||
summary: Getting the latest version of the backend
|
||||
tags:
|
||||
- version
|
||||
/version/history:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: get version history
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/backend.VersionData'
|
||||
type: array
|
||||
"404":
|
||||
description: Not Found
|
||||
schema:
|
||||
type: string
|
||||
summary: Getting the version history of the backend
|
||||
tags:
|
||||
- version
|
||||
swagger: "2.0"
|
||||
@@ -1,21 +0,0 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
@@ -1,24 +0,0 @@
|
||||
# music-randomizer-vue
|
||||
|
||||
## Project setup
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||
@@ -1,5 +0,0 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
const express = require("express");
|
||||
const path = require("path");
|
||||
const routes = require("./routes.js");
|
||||
const app = express();
|
||||
const cors = require("cors");
|
||||
/* const bodyParser = require("body-parser"); */
|
||||
const PORT = process.env.PORT || 5005;
|
||||
const mainUrl = "test";
|
||||
|
||||
|
||||
console.log(`Server started on port ${PORT}`);
|
||||
|
||||
//app.use(bodyParser.json());
|
||||
|
||||
//Routes
|
||||
/* app.use(express.static(path.join(__dirname, "public"))); */
|
||||
/* app.use(express.static(path.join(__dirname, "assets"))); */
|
||||
app.use(express.static(path.join(__dirname)));
|
||||
app.use(express.static(path.join(__dirname, "dist")));
|
||||
/* app.use(express.static(path.join(__dirname, "public"))); */
|
||||
|
||||
/* console.log(__dirname); */
|
||||
app.use(cors());
|
||||
app.use(routes);
|
||||
|
||||
app.listen(PORT, "0.0.0.0");
|
||||
@@ -1,60 +0,0 @@
|
||||
{
|
||||
"name": "music-randomizer-vue",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.7.2",
|
||||
"core-js": "^3.37.1",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.19.2",
|
||||
"vue": "^3.4.31",
|
||||
"vue-axios": "^3.5.2",
|
||||
"vuex": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "^5.0.8",
|
||||
"@vue/cli-plugin-eslint": "^5.0.8",
|
||||
"@vue/cli-service": "^5.0.8",
|
||||
"@vue/compiler-sfc": "^3.4.31",
|
||||
"@babel/eslint-parser": "^7.25.1",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-vue": "^9.27.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/vue3-essential",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": "@babel/eslint-parser",
|
||||
"requireConfigFile": false,
|
||||
"ecmaVersion": 2020,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"globalReturn": false,
|
||||
"impliedStrict": true,
|
||||
"jsx": true
|
||||
},
|
||||
"babelOptions": {
|
||||
"presets": ["@babel/preset-env"]
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"no-debugger": 1
|
||||
}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
]
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="black" width="36px" height="36px"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z"/></svg>
|
||||
|
Before Width: | Height: | Size: 330 B |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 222 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 224 KiB |
|
Before Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 302 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 129 KiB |
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 161 KiB |
|
Before Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 136 KiB |
|
Before Width: | Height: | Size: 185 KiB |
|
Before Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 788 B |
|
Before Width: | Height: | Size: 4.2 KiB |
@@ -1,19 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="black" width="36px" height="36px"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"/></svg>
|
||||
|
Before Width: | Height: | Size: 212 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="black" width="36px" height="36px"><path d="M0 0h24v24H0z" fill="none"/><path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"/></svg>
|
||||
|
Before Width: | Height: | Size: 201 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" viewBox="0 0 24 24" fill="black" width="36px" height="36px"><g><rect fill="none" height="24" width="24"/></g><g><path d="M13,8c0-2.21-1.79-4-4-4S5,5.79,5,8s1.79,4,4,4S13,10.21,13,8z M15,10v2h3v3h2v-3h3v-2h-3V7h-2v3H15z M1,18v2h16v-2 c0-2.66-5.33-4-8-4S1,15.34,1,18z"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 352 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" viewBox="0 0 24 24" fill="black" width="36px" height="36px"><g><rect fill="none" height="24" width="24"/></g><g><path d="M14,8c0-2.21-1.79-4-4-4S6,5.79,6,8s1.79,4,4,4S14,10.21,14,8z M17,10v2h6v-2H17z M2,18v2h16v-2c0-2.66-5.33-4-8-4 S2,15.34,2,18z"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 333 B |
@@ -1,147 +0,0 @@
|
||||
<template>
|
||||
<the-header @start-sound-test="startSoundTest"></the-header>
|
||||
<the-main ref="theMain"></the-main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TheHeader from "./components/layout/TheHeader.vue";
|
||||
import TheMain from "./components/layout/TheMain.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TheHeader,
|
||||
TheMain,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
screenWidth: 1000,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
displayWhenDesktop() {
|
||||
if (this.screenWidth > 600) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
startSoundTest() {
|
||||
this.$refs.theMain.startSoundTest();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.screenWidth = window.innerWidth;
|
||||
window.addEventListener("resize", () => (this.screenWidth = window.innerWidth));
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: Helvetica;
|
||||
}
|
||||
|
||||
button {
|
||||
background: rgb(54, 54, 54);
|
||||
border-radius: 10px;
|
||||
border-color: #ff9c00;
|
||||
color: #ff9c00;
|
||||
font-size: 1em;
|
||||
padding: 10px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.app {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #555;
|
||||
}
|
||||
|
||||
/* Global modal stuff */
|
||||
|
||||
.modalAni-enter-from,
|
||||
.modalAni-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
.modalAni-enter-to,
|
||||
.modalAni-leave-from {
|
||||
opacity: 1;
|
||||
}
|
||||
.modalAni-enter-active,
|
||||
.modalAni-leave-active {
|
||||
transition: all 0.25s ease-out;
|
||||
}
|
||||
|
||||
.modal {
|
||||
position: fixed; /* Stay in place */
|
||||
z-index: 10; /* Sit on top */
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%; /* Full width */
|
||||
height: 100%; /* Full height */
|
||||
overflow: hidden;
|
||||
background-color: rgb(0, 0, 0); /* Fallback color */
|
||||
background-color: rgba(0, 0, 0, 0.5); /* Black w/ opacity */
|
||||
}
|
||||
.modalContainer {
|
||||
background-color: #eeeeee;
|
||||
margin: 10% auto; /* 15% from the top and centered */
|
||||
border: 1px solid #888;
|
||||
width: 50%; /* Could be more or less, depending on screen size */
|
||||
}
|
||||
.modalWrapper {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-wrap: wrap;
|
||||
padding: 10px 40px;
|
||||
}
|
||||
|
||||
.modalWrapper:last-child {
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
.closeModalImg {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 8px;
|
||||
cursor: pointer;
|
||||
opacity: 70%;
|
||||
}
|
||||
.modal h1 {
|
||||
color: black;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding-top: 10px;
|
||||
font-size: 1.9rem;
|
||||
}
|
||||
.modal p {
|
||||
color: black;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1000px) {
|
||||
.modalContainer {
|
||||
margin: 10% auto; /* 15% from the top and centered */
|
||||
width: 93%; /* Could be more or less, depending on screen size */
|
||||
}
|
||||
.modalWrapper {
|
||||
padding: 10px 10px;
|
||||
}
|
||||
|
||||
.modalWrapper:last-child {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.modal h1 {
|
||||
padding-top: 10px;
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,3 +0,0 @@
|
||||
export default {
|
||||
hostname: "$HOSTNAME",
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
<template>
|
||||
<!-- <div class="currentlyPlayingDiv">
|
||||
<div class="textWrapper">
|
||||
<transition-group name="displayingInfo" appear>
|
||||
<p v-if="currentTrackHidden" :key="1">????????</p>
|
||||
<p v-else :key="2">{{ currentGame }}</p>
|
||||
<p v-if="currentTrackHidden" :key="3">??????</p>
|
||||
<p v-else :key="4">{{ currentTrack }}</p>
|
||||
</transition-group>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="currentlyPlayingDiv">
|
||||
<transition name="displayingInfo">
|
||||
<div v-if="currentTrackHidden" class="textWrapper">
|
||||
<p>????????</p>
|
||||
<p>??????</p>
|
||||
</div>
|
||||
<div v-else class="textWrapper">
|
||||
<p>{{ currentGame }}</p>
|
||||
<p>{{ currentTrack }}</p>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
export default {
|
||||
computed: {
|
||||
...mapState(["currentGame", "currentTrack", "currentTrackHidden"]),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.displayingInfo-enter-from,
|
||||
.displayingInfo-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
.displayingInfo-enter-to,
|
||||
.displayingInfo-leave-from {
|
||||
opacity: 1;
|
||||
}
|
||||
.displayingInfo-enter-active {
|
||||
transition: opacity 0.7s ease-out;
|
||||
}
|
||||
|
||||
.displayingInfo-leave-active {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.currentlyPlayingDiv {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
width: 60vw;
|
||||
height: 27vh;
|
||||
margin-right: 0.5vw;
|
||||
color: #ff8800;
|
||||
background-color: #333;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
.currentlyPlayingDiv .textWrapper {
|
||||
padding: 0.75vw;
|
||||
}
|
||||
.currentlyPlayingDiv .textWrapper p {
|
||||
width: 100%;
|
||||
font-size: 2.3em;
|
||||
}
|
||||
|
||||
.currentlyPlayingDiv p:first-child {
|
||||
margin-top: 1.5vw;
|
||||
margin-bottom: 1.5vw;
|
||||
font-size: 2.8em;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1000px) {
|
||||
.currentlyPlayingDiv {
|
||||
width: 100vw;
|
||||
height: 35vw;
|
||||
margin-right: 0;
|
||||
border-radius: 0px;
|
||||
align-content: center;
|
||||
}
|
||||
.currentlyPlayingDiv .textWrapper p {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
.currentlyPlayingDiv p:first-child {
|
||||
margin-bottom: 2vw;
|
||||
font-size: 1.4em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,170 +0,0 @@
|
||||
<template>
|
||||
<div class="inspirationDiv">
|
||||
<div class="inspirationHeader">
|
||||
<div class="inspirationTitle">
|
||||
<h1>Inspiration</h1>
|
||||
</div>
|
||||
<div class="searchInput">
|
||||
<input
|
||||
v-model="searchInputText"
|
||||
type="text"
|
||||
@input="searchGame()"
|
||||
ref="inputField"
|
||||
/>
|
||||
</div>
|
||||
<button @click="clearSearch()">Clear</button>
|
||||
</div>
|
||||
<transition-group
|
||||
tag="ul"
|
||||
name="inspirationList"
|
||||
class="inspirationList"
|
||||
ref="inspirationList"
|
||||
>
|
||||
<li v-for="game in showingGamesList" class="inspirationEntry" :key="game">
|
||||
{{ game }}
|
||||
</li>
|
||||
</transition-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import arne from '../../arne.js'
|
||||
import {mapState} from "vuex";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
allGamesList: [],
|
||||
showingGamesList: [],
|
||||
scrollDown: true,
|
||||
searchInputText: "",
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(["reloadGamesList"]),
|
||||
},
|
||||
watch: {
|
||||
reloadGamesList(newValue) {
|
||||
if (newValue) {
|
||||
this.reloadGames();
|
||||
this.$store.dispatch("reloadGamesList", false);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clearSearch() {
|
||||
this.searchInputText = "";
|
||||
this.searchGame();
|
||||
},
|
||||
searchGame() {
|
||||
this.showingGamesList = [];
|
||||
for (const game of this.allGamesList) {
|
||||
if (this.searchInputText === "" ||
|
||||
game.toLowerCase().replace(/\s/g, "")
|
||||
.includes(this.searchInputText.toLowerCase().replace(/\s/g, ""))) {
|
||||
this.showingGamesList.push(game);
|
||||
}
|
||||
}
|
||||
if (this.searchInputText.replace(/\s/g, "") !== "") {
|
||||
this.showingGamesList.sort((n1, n2) => {
|
||||
if (n1 > n2) {
|
||||
return 1;
|
||||
}
|
||||
if (n1 < n2) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
},
|
||||
scrollInspiration() {
|
||||
let inspirationListDOM = document.querySelector(".inspirationList");
|
||||
let scrollSpeed = 1;
|
||||
let currentScrollLocation = 0;
|
||||
|
||||
currentScrollLocation = inspirationListDOM.scrollTop;
|
||||
this.scrollDown
|
||||
? inspirationListDOM.scrollBy(0, scrollSpeed)
|
||||
: inspirationListDOM.scrollBy(0, -scrollSpeed);
|
||||
|
||||
if (currentScrollLocation === inspirationListDOM.scrollTop) {
|
||||
this.scrollDown = !this.scrollDown;
|
||||
}
|
||||
},
|
||||
reloadGames() {
|
||||
this.axios({
|
||||
method: "get",
|
||||
url: `${arne.hostname}/music/all`,
|
||||
})
|
||||
.then((response) => {
|
||||
this.allGamesList = response.data;
|
||||
this.searchInputText = "";
|
||||
this.searchGame();
|
||||
this.$store.dispatch("updateHowManyGames", this.allGamesList.length);
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.reloadGames();
|
||||
window.setInterval(() => {
|
||||
this.scrollInspiration();
|
||||
}, 40);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.inspirationDiv {
|
||||
width: 38vw;
|
||||
height: 20vh;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.inspirationTitle {
|
||||
display: flex;
|
||||
flex: auto;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.searchInput {
|
||||
display: flex;
|
||||
flex: auto;
|
||||
align-items: flex-end;
|
||||
justify-content: flex-end;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.inspirationHeader {
|
||||
display: flex;
|
||||
background-color: #333;
|
||||
height: 5vh;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.inspirationList {
|
||||
display: inline-block;
|
||||
background-color: rgb(66, 66, 66);
|
||||
width: 38vw;
|
||||
height: 22vh;
|
||||
list-style: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.inspirationEntry {
|
||||
width: 100%;
|
||||
max-width: 38vw;
|
||||
padding: 0.3vh 0.8vh;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1000px) {
|
||||
.inspirationDiv {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,78 +0,0 @@
|
||||
<template>
|
||||
<transition name="modalAni">
|
||||
<div v-if="show" class="modal" @click="checkIfClickShouldCloseModal">
|
||||
<div class="modalContainer">
|
||||
<div class="modalWrapper">
|
||||
<img
|
||||
class="closeModalImg"
|
||||
src="cancel-black-36dp.svg"
|
||||
alt="closeModalIMG"
|
||||
@click="closeModal"
|
||||
/>
|
||||
<h1>Music Player Randomizer v0.1</h1>
|
||||
<p class="descriptionText">
|
||||
Try your video game music knowledge with this VGM randomizer,
|
||||
invite your friends and see who is the best.
|
||||
</p>
|
||||
<p class="creditText">
|
||||
Back-end done by Sebastian Olsson,
|
||||
<a href="https://twitter.com/Raida91">Twitter</a>
|
||||
</p>
|
||||
<p class="creditText">
|
||||
Front-end done by Peter Johansson,
|
||||
<a href="https://twitter.com/WebDevPJ">Twitter</a>
|
||||
</p>
|
||||
<!-- <p class="patchNotesText">
|
||||
Full patch notes can be found
|
||||
<a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ">here</a>
|
||||
</p> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
closeModal() {
|
||||
this.show = false;
|
||||
},
|
||||
openModal() {
|
||||
this.show = true;
|
||||
},
|
||||
checkIfClickShouldCloseModal(event) {
|
||||
if (event.target.classList[0] === "modal") {
|
||||
this.closeModal();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
p {
|
||||
font-size: 1.2rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.descriptionText {
|
||||
margin-top: 35px;
|
||||
}
|
||||
|
||||
.creditText {
|
||||
margin-top: 30px;
|
||||
}
|
||||
.creditText:nth-child(5) {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.patchNotesText {
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,160 +0,0 @@
|
||||
<template>
|
||||
<transition name="modalAni">
|
||||
<div v-if="show" class="modal" @click="checkIfClickShouldCloseModal">
|
||||
<div class="modalContainer">
|
||||
<div class="modalWrapper">
|
||||
<img
|
||||
class="closeModalImg"
|
||||
src="cancel-black-36dp.svg"
|
||||
alt="closeModalIMG"
|
||||
@click="closeModal"
|
||||
/>
|
||||
<h1 @click="test">{{ playerName }}, choose your fighter!</h1>
|
||||
<div class="fighterDiv">
|
||||
<div class="fighter" @click="chooseFighter('Adol')">
|
||||
<img src="characters/Adol.png" alt="adol" />
|
||||
<p>Adol Christin</p>
|
||||
</div>
|
||||
<div class="fighter" @click="chooseFighter('Link')">
|
||||
<img src="characters/Link.png" alt="link" />
|
||||
<p>Link</p>
|
||||
</div>
|
||||
<div class="fighter" @click="chooseFighter('Barbarian')">
|
||||
<img src="characters/Barbarian.png" alt="barbarian" />
|
||||
<p>Barbarian</p>
|
||||
</div>
|
||||
<div class="fighter" @click="chooseFighter('Layton')">
|
||||
<img src="characters/Layton.png" alt="layton" />
|
||||
<p>Professor Layton</p>
|
||||
</div>
|
||||
<div class="fighter" @click="chooseFighter('Kiryu')">
|
||||
<img src="characters/Kiryu.png" alt="kiryu" />
|
||||
<p>Kazuma Kiryu</p>
|
||||
</div>
|
||||
<div class="fighter" @click="chooseFighter('Miles')">
|
||||
<img src="characters/Miles.png" alt="miles" />
|
||||
<p>Miles Edgeworth</p>
|
||||
</div>
|
||||
<div class="fighter" @click="chooseFighter('Lemmings')">
|
||||
<img src="characters/Lemmings.png" alt="lemmings" />
|
||||
<p>Lemmings</p>
|
||||
</div>
|
||||
<div class="fighter" @click="chooseFighter('Samus')">
|
||||
<img src="characters/Samus.png" alt="samus" />
|
||||
<p>Samus</p>
|
||||
</div>
|
||||
<div class="fighter" @click="chooseFighter('Kratos')">
|
||||
<img src="characters/Kratos.png" alt="kratos" />
|
||||
<p>Kratos</p>
|
||||
</div>
|
||||
<div class="fighter" @click="chooseFighter('Aloy')">
|
||||
<img src="characters/Aloy.png" alt="aloy" />
|
||||
<p>Aloy</p>
|
||||
</div>
|
||||
<div class="fighter" @click="chooseFighter('Sora')">
|
||||
<img src="characters/Sora.png" alt="sora" />
|
||||
<p>Sora</p>
|
||||
</div>
|
||||
<div class="fighter" @click="chooseFighter('Raiden')">
|
||||
<img src="characters/Raiden.png" alt="raiden" />
|
||||
<p>Raiden</p>
|
||||
</div>
|
||||
<div class="fighter" @click="chooseFighter('PaperMario')">
|
||||
<img src="characters/PaperMario.png" alt="paper mario" />
|
||||
<p>Paper Mario</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
playerName: "",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
closeModal() {
|
||||
this.show = false;
|
||||
},
|
||||
openModal(playerName) {
|
||||
this.playerName = playerName;
|
||||
this.show = true;
|
||||
},
|
||||
checkIfClickShouldCloseModal(event) {
|
||||
if (event.target.classList[0] === "modal") {
|
||||
this.closeModal();
|
||||
}
|
||||
},
|
||||
chooseFighter(profilePicSrc) {
|
||||
let payload = {
|
||||
playerName: this.playerName,
|
||||
profile: "characters/" + profilePicSrc + ".png",
|
||||
};
|
||||
this.$store.dispatch("changePlayerProfile", payload);
|
||||
this.closeModal();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modalContainer {
|
||||
width: 80%; /* Could be more or less, depending on screen size */
|
||||
}
|
||||
.fighterDiv {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
margin-top: 30px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.fighterDiv img {
|
||||
width: 180px;
|
||||
height: 90px;
|
||||
}
|
||||
|
||||
.fighter {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
width: 14vw;
|
||||
margin-top: 30px;
|
||||
}
|
||||
.fighter > * {
|
||||
width: 100%;
|
||||
}
|
||||
.fighter:hover {
|
||||
outline: 1px solid rgb(128, 83, 0);
|
||||
outline-offset: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.fighter p {
|
||||
margin-top: 8px;
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1000px) {
|
||||
.modalContainer {
|
||||
width: 95%; /* Could be more or less, depending on screen size */
|
||||
}
|
||||
.fighterDiv {
|
||||
margin-top: 30px;
|
||||
}
|
||||
.fighterDiv img {
|
||||
width: 80px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.fighter {
|
||||
width: 25vw;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,111 +0,0 @@
|
||||
<template>
|
||||
<div class="extraButtonsDiv">
|
||||
<button @click="resetPlaylist">Reset playlist</button>
|
||||
<button @click="resetPoints">Reset points</button>
|
||||
<button @click="syncGames">Sync games</button>
|
||||
<button @click="startSoundTest">Sound test</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import arne from '../../arne.js'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
emptyPlaylist: [],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async resetPoints() {
|
||||
this.$store.dispatch("resetPlayerScore");
|
||||
this.$store.dispatch("resetPlayerWelcomed");
|
||||
this.$store.dispatch("resetPlayerMatchPoint");
|
||||
this.$store.dispatch("setRoundStarted", false);
|
||||
},
|
||||
async resetPlaylist() {
|
||||
await this.APIresetPlaylist();
|
||||
this.$store.dispatch("updatePlaylistHistory", this.emptyPlaylist);
|
||||
this.$store.dispatch("setCurrentlyLoadingTrack", "N/A");
|
||||
this.$store.dispatch("setCurrentTrackHidden", false);
|
||||
},
|
||||
async syncGames() {
|
||||
await this.APIsyncGames();
|
||||
await this.APIresetPlaylist();
|
||||
this.$store.dispatch("resetPlayerScore");
|
||||
this.$store.dispatch("resetPlayerWelcomed");
|
||||
this.$store.dispatch("resetPlayerMatchPoint");
|
||||
this.$store.dispatch("setRoundStarted", false);
|
||||
this.$store.dispatch("updatePlaylistHistory", this.emptyPlaylist);
|
||||
this.$store.dispatch("setCurrentlyLoadingTrack", "N/A");
|
||||
this.$store.dispatch("setCurrentTrackHidden", false);
|
||||
this.$store.dispatch("reloadGamesList", true);
|
||||
},
|
||||
startSoundTest() {
|
||||
this.$emit("start-sound-test");
|
||||
},
|
||||
APIresetPlaylist() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.axios({
|
||||
method: "get",
|
||||
url: `${arne.hostname}/music/reset`,
|
||||
})
|
||||
.then(() => {
|
||||
resolve();
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.log(error);
|
||||
reject();
|
||||
});
|
||||
});
|
||||
},
|
||||
APIsyncGames() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.axios({
|
||||
method: "get",
|
||||
url: `${arne.hostname}/sync`,
|
||||
})
|
||||
.then(() => {
|
||||
resolve();
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.log(error);
|
||||
reject();
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.extraButtonsDiv {
|
||||
display: flex;
|
||||
margin-top: 2px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 65%;
|
||||
margin-right: 0.4vw;
|
||||
border-radius: 10px;
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1000px) {
|
||||
.extraButtonsDiv {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 51vw;
|
||||
height: 40px;
|
||||
margin-bottom: 10px;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,136 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="show"
|
||||
class="mobileMenu"
|
||||
@click="checkIfClickShouldCloseMobileMenu"
|
||||
>
|
||||
<transition name="mobileMenuAni">
|
||||
<div v-if="showContent" class="mobileMenuContainer">
|
||||
<div class="mobileMenuWrapper">
|
||||
<h1>Menu</h1>
|
||||
<button class="settingsButton" @click="showSettingsModal">
|
||||
Settings
|
||||
</button>
|
||||
<button class="statisticsButton" @click="showStatisticsModal">
|
||||
Statistics
|
||||
</button>
|
||||
<button class="aboutButton" @click="showAboutModal">
|
||||
About
|
||||
</button>
|
||||
<h1>Control</h1>
|
||||
<extra-buttons
|
||||
@start-sound-test="startSoundTest"
|
||||
></extra-buttons>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import extraButtons from "./extraButtons.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
extraButtons,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
showContent: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
closeMobileMenu() {
|
||||
this.showContent = false;
|
||||
setTimeout(() => {
|
||||
this.show = false;
|
||||
}, 250);
|
||||
},
|
||||
openMobileMenu() {
|
||||
this.show = true;
|
||||
},
|
||||
openMobileMenuContent() {
|
||||
this.showContent = true;
|
||||
},
|
||||
checkIfClickShouldCloseMobileMenu(event) {
|
||||
if (event.target.classList[0] === "mobileMenu") {
|
||||
this.closeMobileMenu();
|
||||
}
|
||||
},
|
||||
showSettingsModal() {
|
||||
this.show = false;
|
||||
this.showContent = false;
|
||||
this.$emit("show-settings-modal");
|
||||
},
|
||||
showStatisticsModal() {
|
||||
this.show = false;
|
||||
this.showContent = false;
|
||||
this.$emit("show-statistics-modal");
|
||||
},
|
||||
showAboutModal() {
|
||||
this.show = false;
|
||||
this.showContent = false;
|
||||
this.$emit("show-about-modal");
|
||||
},
|
||||
startSoundTest() {
|
||||
this.$emit("start-sound-test");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mobileMenuAni-enter-from,
|
||||
.mobileMenuAni-leave-to {
|
||||
opacity: 0.6;
|
||||
transform: translateX(80vw);
|
||||
}
|
||||
.mobileMenuAni-enter-to,
|
||||
.mobileMenuAni-leave-from {
|
||||
opacity: 1;
|
||||
transform: translateX(0vw);
|
||||
}
|
||||
.mobileMenuAni-enter-active,
|
||||
.mobileMenuAni-leave-active {
|
||||
transition: all 0.3s ease-out;
|
||||
}
|
||||
|
||||
.mobileMenu {
|
||||
position: fixed;
|
||||
z-index: 8;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background-color: rgb(0, 0, 0);
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.mobileMenuContainer {
|
||||
background-color: #c4c4c4;
|
||||
width: 80vw;
|
||||
height: 100%;
|
||||
margin-left: 20%;
|
||||
}
|
||||
|
||||
.mobileMenuWrapper {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 10px 0px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 51vw;
|
||||
height: 40px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,354 +0,0 @@
|
||||
<template>
|
||||
<div class="playersWindowDiv">
|
||||
<div @click="toggleDisplay" class="playerListTitle">
|
||||
<img v-if="display" class="arrow arrowToggle" src="keyboard_arrow_up-black-36dp.svg" />
|
||||
<img v-else class="arrow arrowToggle" src="keyboard_arrow_down-black-36dp.svg" />
|
||||
<h1>Players</h1>
|
||||
<img
|
||||
src="person_add_alt_1-black-36dp.svg"
|
||||
alt="addPlayer"
|
||||
class="addPlayerIMG"
|
||||
@click="toggleNewPlayerInput()"
|
||||
/>
|
||||
</div>
|
||||
<transition-group tag="div" v-if="display" name="playerList" class="playerList">
|
||||
<div class="player" v-for="player in listOfPlayers" :key="player.playerName">
|
||||
<p>{{ player.playerName }}: {{ player.score }}</p>
|
||||
<img
|
||||
class="profilePicture"
|
||||
:src="player.profile"
|
||||
@click="openCharacterModal(player.playerName)"
|
||||
/>
|
||||
<button @click="changeScore(player.playerName, 1)">+1</button>
|
||||
<button @click="changeScore(player.playerName, -1)">-1</button>
|
||||
<img
|
||||
src="person_remove-black-36dp.svg"
|
||||
@click="removePlayer(player.playerName)"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<div class="newPlayerInput" v-if="showNewPlayerInput === true">
|
||||
<input
|
||||
v-model="newPlayerInputText"
|
||||
type="text"
|
||||
@keypress.enter="addNewPlayer()"
|
||||
ref="inputField"
|
||||
placeholder="Player name"
|
||||
/>
|
||||
<button @click="addNewPlayer()">Add</button>
|
||||
</div>
|
||||
</transition-group>
|
||||
<winning-modal ref="winningModal"></winning-modal>
|
||||
<character-modal ref="characterModal"></character-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
import winningModal from "../items/winningModal.vue";
|
||||
import characterModal from "../items/characterModal.vue";
|
||||
export default {
|
||||
components: {
|
||||
winningModal,
|
||||
characterModal,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showNewPlayerInput: false,
|
||||
newPlayerInputText: "",
|
||||
display: true,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(["listOfPlayers", "winningScore"]),
|
||||
},
|
||||
methods: {
|
||||
removePlayer(playerName) {
|
||||
this.$store.dispatch("removePlayer", playerName);
|
||||
},
|
||||
newPlayerValidation() {
|
||||
/* Validation variables */
|
||||
let errors = [];
|
||||
let newName = this.newPlayerInputText;
|
||||
|
||||
/* Check for empty value */
|
||||
if (newName.trim() === "") {
|
||||
errors.push("No player name was entered");
|
||||
}
|
||||
|
||||
/* Prepare data of players and check for already entered ones */
|
||||
let copyOfPlayerList = this.listOfPlayers;
|
||||
let arrayOfPlayers = copyOfPlayerList.map((player) => player.playerName.toLowerCase());
|
||||
if (arrayOfPlayers.includes(newName.toLowerCase())) {
|
||||
errors.push("This player already exists");
|
||||
}
|
||||
|
||||
/* Check amount of players */
|
||||
if (arrayOfPlayers.length > 6) {
|
||||
errors.push("Too many players already exists");
|
||||
}
|
||||
|
||||
/* If everything went fine, return an empty */
|
||||
return errors;
|
||||
},
|
||||
addNewPlayer() {
|
||||
let errors = this.newPlayerValidation();
|
||||
if (errors.length === 0) {
|
||||
this.$store.dispatch("addNewPlayer", this.newPlayerInputText);
|
||||
this.newPlayerInputText = "";
|
||||
} else {
|
||||
console.log(errors);
|
||||
}
|
||||
},
|
||||
toggleNewPlayerInput() {
|
||||
this.showNewPlayerInput = !this.showNewPlayerInput;
|
||||
if (this.showNewPlayerInput === true) {
|
||||
this.$nextTick(() => this.$refs.inputField.focus());
|
||||
}
|
||||
},
|
||||
changeScore(playerName, score) {
|
||||
let payload = {
|
||||
playerName: playerName,
|
||||
score: score,
|
||||
};
|
||||
this.$store.dispatch("changePlayerScore", payload);
|
||||
for (let i = 0; i < this.listOfPlayers.length; i++) {
|
||||
/* If a player received their first point */
|
||||
if (this.listOfPlayers[i].score == 1 && this.listOfPlayers[i].welcomed === false) {
|
||||
let payload = {
|
||||
playerName: playerName,
|
||||
welcomeSet: true,
|
||||
};
|
||||
this.$store.dispatch("changePlayerWelcomed", payload);
|
||||
this.$emit("play-welcome-sound");
|
||||
}
|
||||
/* If a player reach match point */
|
||||
if (this.listOfPlayers[i].score === this.winningScore-1) {
|
||||
let payload = {
|
||||
playerName: playerName,
|
||||
matchPointSet: true,
|
||||
};
|
||||
this.$store.dispatch("changePlayerMatchPoint", payload);
|
||||
this.$emit("play-match-point-sound");
|
||||
}
|
||||
/* If a player won */
|
||||
if (this.listOfPlayers[i].score === this.winningScore) {
|
||||
this.$refs.winningModal.openModal(this.listOfPlayers[i].playerName);
|
||||
this.$emit("play-winning-sound");
|
||||
}
|
||||
}
|
||||
},
|
||||
openCharacterModal(playerName) {
|
||||
this.$refs.characterModal.openModal(playerName);
|
||||
},
|
||||
getProfileSrc(profileToGet) {
|
||||
console.log(this.listOfPlayers[0]);
|
||||
let src = `characters/${profileToGet}`;
|
||||
console.log(src);
|
||||
return src;
|
||||
},
|
||||
toggleDisplay(event) {
|
||||
if (event.target.classList[0] != "addPlayerIMG") {
|
||||
if (window.innerWidth < 600) {
|
||||
this.display = !this.display;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.playerList-enter-from,
|
||||
.playerList-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
.playerList-enter-to,
|
||||
.playerList-leave-from {
|
||||
opacity: 1;
|
||||
}
|
||||
.playerList-enter-active {
|
||||
transition: all 0.5s ease-out;
|
||||
}
|
||||
.playerList-leave-active {
|
||||
position: absolute;
|
||||
transition: all 0.2s ease-in;
|
||||
}
|
||||
.playerList-move {
|
||||
transition: 0.25s;
|
||||
transition-property: transform, opacity;
|
||||
}
|
||||
.playerList-move.newPlayerInput {
|
||||
transition: 0.2s;
|
||||
transition-property: transform;
|
||||
}
|
||||
/* End of Animation block */
|
||||
|
||||
.playersWindowDiv {
|
||||
display: inline-block;
|
||||
width: 38vw;
|
||||
color: white;
|
||||
}
|
||||
.playerListTitle {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
background-color: #333;
|
||||
height: 5vh;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
.playerListTitle h1 {
|
||||
text-align: center;
|
||||
position: relative;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
flex-grow: 100;
|
||||
}
|
||||
.playerListTitle .addPlayerIMG {
|
||||
justify-self: flex-end;
|
||||
margin-left: auto;
|
||||
margin-right: 5px;
|
||||
flex-grow: 1;
|
||||
filter: invert(66%) sepia(96%) saturate(2783%) hue-rotate(1deg) brightness(105%) contrast(103%);
|
||||
}
|
||||
.playerListTitle .arrow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.playerListTitle .arrowToggle {
|
||||
display: none;
|
||||
}
|
||||
.playerList {
|
||||
display: inline-block;
|
||||
background-color: rgb(66, 66, 66);
|
||||
min-height: 37vh;
|
||||
}
|
||||
.playerList .player {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-end;
|
||||
width: 38vw;
|
||||
height: 6vh;
|
||||
font-size: 1.8rem;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.playerList .player p {
|
||||
margin-left: 5px;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.playerList .player .profilePicture {
|
||||
width: 100px;
|
||||
height: 50px;
|
||||
background-color: red;
|
||||
margin-right: 2vw;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.playerList .player button {
|
||||
align-content: center;
|
||||
padding: 3px;
|
||||
width: 50px;
|
||||
height: 4vh;
|
||||
margin-right: 0.6vw;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.playerList .player button:last-of-type {
|
||||
margin-right: 3vw;
|
||||
}
|
||||
|
||||
.playerList .player img {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.newPlayerInput {
|
||||
display: flex;
|
||||
width: 38vw;
|
||||
height: 50px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 5px 0;
|
||||
background-color: rgb(107, 106, 106);
|
||||
}
|
||||
|
||||
.newPlayerInput p {
|
||||
font-size: 1.3rem;
|
||||
flex-grow: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.newPlayerInput input {
|
||||
height: 70%;
|
||||
width: 250px;
|
||||
font-size: 1.5rem;
|
||||
margin: 0 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.newPlayerInput button {
|
||||
margin: 0 10px;
|
||||
height: 80%;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1000px) {
|
||||
.playersWindowDiv {
|
||||
width: 100%;
|
||||
}
|
||||
.playerListTitle {
|
||||
height: 10vw;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.playerListTitle .addPlayerIMG {
|
||||
height: 9vw;
|
||||
}
|
||||
.playerListTitle .arrowToggle {
|
||||
display: block;
|
||||
max-height: 100%;
|
||||
left: 0;
|
||||
filter: invert(77%) sepia(8%) saturate(6%) hue-rotate(317deg) brightness(95%) contrast(87%);
|
||||
}
|
||||
.playerList {
|
||||
width: 100%;
|
||||
height: 60vw;
|
||||
min-height: 190px;
|
||||
overflow: scroll;
|
||||
}
|
||||
.playerList .player {
|
||||
width: 100vw;
|
||||
height: 12vw;
|
||||
font-size: 1rem;
|
||||
}
|
||||
.playerList .player .profilePicture {
|
||||
width: 60px;
|
||||
height: 30px;
|
||||
margin-right: 1.5vw;
|
||||
}
|
||||
.playerList .player button {
|
||||
align-content: center;
|
||||
padding: 3px;
|
||||
width: 45px;
|
||||
height: 10vw;
|
||||
margin-right: 1vw;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.newPlayerInput {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
}
|
||||
.newPlayerInput input {
|
||||
height: 90%;
|
||||
width: 200px;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
.newPlayerInput button {
|
||||
margin: 0 10px;
|
||||
height: 40px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,191 +0,0 @@
|
||||
<template>
|
||||
<div class="playlistHistoryDiv">
|
||||
<div @click="toggleDisplay" class="playlistHistoryTitle">
|
||||
<h1>Playlist history</h1>
|
||||
<img v-if="display" src="keyboard_arrow_up-black-36dp.svg" alt="" />
|
||||
<img v-else src="keyboard_arrow_down-black-36dp.svg" alt="" />
|
||||
</div>
|
||||
<transition-group
|
||||
tag="div"
|
||||
v-if="display"
|
||||
name="playlistHistory"
|
||||
class="playlistHistory"
|
||||
ref="playlistHistory"
|
||||
>
|
||||
<div
|
||||
class="track"
|
||||
v-for="track in playlistHistory"
|
||||
:key="track.SongNo"
|
||||
@click="playSelectedTrack(track, $event)"
|
||||
>
|
||||
<p
|
||||
v-if="checkIfHidden(track, playlistHistory)"
|
||||
:class="{ activeTrack: track.CurrentlyPlaying }"
|
||||
>
|
||||
??? - ???
|
||||
</p>
|
||||
<p v-else-if="track.Game !== ''" :class="{ activeTrack: track.CurrentlyPlaying }">
|
||||
{{ track.SongNo + 1 }}. {{ track.Game }} -
|
||||
{{ displayTrack(track.Song) }}
|
||||
</p>
|
||||
<span v-if="currentlyLoadingTrack === track.SongNo" class="loadingTrack">
|
||||
-- Loading --
|
||||
</span>
|
||||
</div>
|
||||
</transition-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
display: true,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(["playlistHistory", "currentTrackHidden", "currentlyLoadingTrack"]),
|
||||
},
|
||||
methods: {
|
||||
checkIfHidden(track, playlistHistory) {
|
||||
return track.SongNo === playlistHistory.length - 1 && this.currentTrackHidden;
|
||||
},
|
||||
displayTrack(trackName) {
|
||||
/* Todo: Is this if-check necessary? */
|
||||
if (trackName === undefined) {
|
||||
return "";
|
||||
} else {
|
||||
return trackName.replace(".mp3", "");
|
||||
}
|
||||
},
|
||||
playSelectedTrack(track, event) {
|
||||
console.log(event.path);
|
||||
this.$store.dispatch("setCurrentlyLoadingTrack", track.SongNo);
|
||||
this.$emit("play-selected-track", track);
|
||||
},
|
||||
scrollToBottom() {
|
||||
if (this.display) {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.playlistHistory.$el.scrollTop = this.$refs.playlistHistory.$el.scrollHeight;
|
||||
});
|
||||
}
|
||||
},
|
||||
toggleDisplay() {
|
||||
if (window.innerWidth < 600) {
|
||||
this.display = !this.display;
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
if (window.innerWidth < 600) {
|
||||
this.display = false;
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
/* Scroll */
|
||||
|
||||
.playlistHistory::-webkit-scrollbar {
|
||||
width: 17px;
|
||||
}
|
||||
.playlistHistory {
|
||||
scrollbar-color: #4f5253c2;
|
||||
}
|
||||
.playlistHistory::-webkit-scrollbar-track {
|
||||
background: #393c4179;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.playlistHistory::-webkit-scrollbar-thumb {
|
||||
background-color: #6d7679c2;
|
||||
border-radius: 5px;
|
||||
border: 2px solid #3c434e79;
|
||||
}
|
||||
|
||||
/* End of Scroll */
|
||||
|
||||
.playlistHistoryDiv {
|
||||
display: inline-block;
|
||||
width: 60vw;
|
||||
color: white;
|
||||
margin-right: 0.5vw;
|
||||
}
|
||||
|
||||
.playlistHistoryTitle {
|
||||
display: flex;
|
||||
background-color: #333;
|
||||
height: 5vh;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.playlistHistoryTitle img {
|
||||
display: none;
|
||||
}
|
||||
.playlistHistory {
|
||||
background-color: rgb(66, 66, 66);
|
||||
width: 60vw;
|
||||
height: 37vh;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.playlistHistory .track {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start;
|
||||
width: 57vw;
|
||||
height: auto;
|
||||
min-height: 30px;
|
||||
line-height: 1.5;
|
||||
font-size: 1.1rem;
|
||||
align-items: center;
|
||||
padding: 0.3vh 0.6vh;
|
||||
cursor: pointer;
|
||||
}
|
||||
.playlistHistory .track:nth-child(even) {
|
||||
background-color: #4e4e4e;
|
||||
}
|
||||
.playlistHistory .activeTrack {
|
||||
color: yellow;
|
||||
}
|
||||
.loadingTrack {
|
||||
color: #ff9c00;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1000px) {
|
||||
.playlistHistory::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
}
|
||||
.playlistHistoryDiv {
|
||||
width: 100%;
|
||||
margin: 0 0 0.3vw;
|
||||
}
|
||||
|
||||
.playlistHistoryTitle {
|
||||
height: 10vw;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.playlistHistoryTitle img {
|
||||
display: block;
|
||||
position: absolute;
|
||||
max-height: 100%;
|
||||
left: 0;
|
||||
filter: invert(77%) sepia(8%) saturate(6%) hue-rotate(317deg) brightness(95%) contrast(87%);
|
||||
}
|
||||
.playlistHistory {
|
||||
width: 100vw;
|
||||
height: 56vw;
|
||||
}
|
||||
.playlistHistory .track {
|
||||
width: 98vw;
|
||||
font-size: 0.8rem;
|
||||
padding: 1.5vw 1vw;
|
||||
}
|
||||
.loadingTrack {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,209 +0,0 @@
|
||||
<template>
|
||||
<!-- Round Settings, Stop after current, Don't hide title -->
|
||||
<transition name="modalAni">
|
||||
<div v-if="show" class="modal" @click="checkIfClickShouldCloseModal">
|
||||
<div class="modalContainer">
|
||||
<div class="modalWrapper">
|
||||
<img
|
||||
class="closeModalImg"
|
||||
src="cancel-black-36dp.svg"
|
||||
alt="closeModalIMG"
|
||||
@click="closeModal"
|
||||
/>
|
||||
<h1>Settings</h1>
|
||||
|
||||
<div class="checkboxDiv">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="stopAfterCurrent"
|
||||
id="stopAfterCurrent"
|
||||
v-model="checkboxStopAfterCurrent"
|
||||
@change="updateOptionStopAfterCurrent"
|
||||
/>
|
||||
<label for="stopAfterCurrent">Stop after current</label>
|
||||
</div>
|
||||
<div class="checkboxDiv">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="hideTitle"
|
||||
id="hideTitle"
|
||||
v-model="checkboxHideTitle"
|
||||
@change="updateOptionHideTitle"
|
||||
/>
|
||||
<label for="hideTitle">Hide next track</label>
|
||||
</div>
|
||||
<div class="checkboxDiv">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="addPlayed"
|
||||
id="addPlayed"
|
||||
v-model="checkboxAddPlayed"
|
||||
@change="updateOptionAddPlayed"
|
||||
/>
|
||||
<label for="addPlayed">Turn on played to database</label>
|
||||
</div>
|
||||
<div class="checkboxDiv">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="lowPlayed"
|
||||
id="lowPlayed"
|
||||
v-model="checkboxLowPlayed"
|
||||
@change="updateOptionLowPlayed"
|
||||
/>
|
||||
<label for="lowPlayed">Use low played mode</label>
|
||||
</div>
|
||||
|
||||
<div class="winningScoreDiv">
|
||||
<span>Winning Score: {{ winningScore }}</span>
|
||||
<button
|
||||
:class="{ disabled: roundStarted }"
|
||||
:disabled="roundStarted"
|
||||
@click="changeScore(-1)"
|
||||
>
|
||||
-1
|
||||
</button>
|
||||
<button
|
||||
:class="{ disabled: roundStarted }"
|
||||
:disabled="roundStarted"
|
||||
@click="changeScore(1)"
|
||||
>
|
||||
+1
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
checkboxStopAfterCurrent: false,
|
||||
checkboxHideTitle: false,
|
||||
checkboxAddPlayed: false,
|
||||
checkboxLowPlayed: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(["winningScore", "roundStarted", "stopAfterCurrent", "hideNextTrack", "addPlayed", "lowPlayed"]),
|
||||
},
|
||||
methods: {
|
||||
closeModal() {
|
||||
this.show = false;
|
||||
},
|
||||
openModal() {
|
||||
this.show = true;
|
||||
},
|
||||
checkIfClickShouldCloseModal(event) {
|
||||
if (event.target.classList[0] === "modal") {
|
||||
this.closeModal();
|
||||
}
|
||||
},
|
||||
updateOptionHideTitle() {
|
||||
if (this.checkboxHideTitle) {
|
||||
this.$store.dispatch("updateHideNextTitle", true);
|
||||
} else {
|
||||
this.$store.dispatch("updateHideNextTitle", false);
|
||||
}
|
||||
},
|
||||
updateOptionAddPlayed() {
|
||||
if (this.checkboxAddPlayed) {
|
||||
this.$store.dispatch("updateAddPlayed", true);
|
||||
} else {
|
||||
this.$store.dispatch("updateAddPlayed", false);
|
||||
}
|
||||
},
|
||||
updateOptionLowPlayed() {
|
||||
if (this.checkboxLowPlayed) {
|
||||
this.$store.dispatch("updateLowPlayed", true);
|
||||
} else {
|
||||
this.$store.dispatch("updateLowPlayed", false);
|
||||
}
|
||||
},
|
||||
updateOptionStopAfterCurrent() {
|
||||
if (this.checkboxStopAfterCurrent) {
|
||||
this.$store.dispatch("updateStopAfterCurrent", true);
|
||||
} else {
|
||||
this.$store.dispatch("updateStopAfterCurrent", false);
|
||||
}
|
||||
},
|
||||
changeScore(score) {
|
||||
if (score < 0) {
|
||||
this.$store.dispatch("setWinningScore", -1);
|
||||
} else {
|
||||
this.$store.dispatch("setWinningScore", 1);
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.checkboxStopAfterCurrent = this.stopAfterCurrent;
|
||||
this.checkboxHideTitle = this.hideNextTrack;
|
||||
this.checkboxAddPlayed = this.addPlayed;
|
||||
this.checkboxLowPlayed = this.lowPlayed;
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.winningScoreDiv {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.checkboxDiv {
|
||||
margin: 5px;
|
||||
width: 100%;
|
||||
}
|
||||
.checkboxDiv:first-of-type {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.checkboxDiv input {
|
||||
transform: scale(1.3);
|
||||
}
|
||||
.checkboxDiv label {
|
||||
margin-left: 10px;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.winningScoreDiv span {
|
||||
font-size: 1.7rem;
|
||||
margin-right: 10px;
|
||||
width: 245px;
|
||||
}
|
||||
.winningScoreDiv button {
|
||||
display: flex;
|
||||
width: 50px;
|
||||
height: 33px;
|
||||
background: rgb(16, 99, 194);
|
||||
color: rgb(235, 235, 235);
|
||||
border-color: rgb(161, 161, 161);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
line-height: 0;
|
||||
font-size: 1.5rem;
|
||||
margin: 3px;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
opacity: 50%;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1000px) {
|
||||
.checkboxDiv label {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.winningScoreDiv span {
|
||||
font-size: 1.3rem;
|
||||
margin-right: 10px;
|
||||
width: 245px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,68 +0,0 @@
|
||||
<template>
|
||||
<transition name="modalAni">
|
||||
<div v-if="show" class="modal" @click="checkIfClickShouldCloseModal">
|
||||
<div class="modalContainer">
|
||||
<div class="modalWrapper">
|
||||
<img
|
||||
class="closeModalImg"
|
||||
src="cancel-black-36dp.svg"
|
||||
alt="closeModalIMG"
|
||||
@click="closeModal"
|
||||
/>
|
||||
<h1>Statistics</h1>
|
||||
<p>Total amount of games in the playlist: {{ howManyGames }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(["howManyGames"]),
|
||||
},
|
||||
methods: {
|
||||
closeModal() {
|
||||
this.show = false;
|
||||
},
|
||||
openModal() {
|
||||
this.show = true;
|
||||
},
|
||||
checkIfClickShouldCloseModal(event) {
|
||||
if (event.target.classList[0] === "modal") {
|
||||
this.closeModal();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
p {
|
||||
font-size: 1.2rem;
|
||||
width: 100%;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.descriptionText {
|
||||
margin-top: 35px;
|
||||
}
|
||||
|
||||
.creditText {
|
||||
margin-top: 30px;
|
||||
}
|
||||
.creditText:nth-child(5) {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.patchNotesText {
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,50 +0,0 @@
|
||||
<template>
|
||||
<transition name="modalAni">
|
||||
<div v-if="show" class="modal" @click="checkIfClickShouldCloseModal">
|
||||
<div class="modalContainer">
|
||||
<div class="modalWrapper">
|
||||
<img
|
||||
class="closeModalImg"
|
||||
src="cancel-black-36dp.svg"
|
||||
alt="closeModalIMG"
|
||||
@click="closeModal"
|
||||
/>
|
||||
<h1>{{ playerName }} won!!</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
playerName: "",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
closeModal() {
|
||||
this.show = false;
|
||||
},
|
||||
showPeter() {
|
||||
let randomNumber = Math.floor(Math.random() * 10);
|
||||
console.log(randomNumber);
|
||||
console.log("Test");
|
||||
return randomNumber < 2;
|
||||
},
|
||||
openModal(playerName) {
|
||||
this.playerName = playerName;
|
||||
this.show = true;
|
||||
},
|
||||
checkIfClickShouldCloseModal(event) {
|
||||
if (event.target.classList[0] === "modal") {
|
||||
this.closeModal();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -1,441 +0,0 @@
|
||||
<template>
|
||||
<footer>
|
||||
<div class="audioAndButtonsDiv">
|
||||
<div class="audioButtonsDiv">
|
||||
<button
|
||||
class="bigButton"
|
||||
:class="{ disabled: disableButtons }"
|
||||
:disabled="disableButtons"
|
||||
@click="randomizeTrack()"
|
||||
>
|
||||
<p v-if="!disableButtons"><u>R</u>andomize new track</p>
|
||||
<p v-else>Loading...</p>
|
||||
</button>
|
||||
<button class="bigButton" @click="showAnswer()"><u>S</u>how answer</button>
|
||||
</div>
|
||||
<div class="audioDiv">
|
||||
<audio
|
||||
controls
|
||||
class="audioPlayer"
|
||||
:src="currentTrackSrcFile"
|
||||
ref="audioPlayer"
|
||||
:onended="trackHasEnded"
|
||||
></audio>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
import arne from "../../arne.js";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
currentTrackSrcFile: "",
|
||||
disableButtons: false,
|
||||
loadingBar: "",
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
"stopAfterCurrent",
|
||||
"localPlaylist",
|
||||
"hideNextTrack",
|
||||
"roundStarted",
|
||||
"specialTrackIsPlaying",
|
||||
"addPlayed",
|
||||
"lowPlayed",
|
||||
]),
|
||||
},
|
||||
methods: {
|
||||
async randomizeTrack() {
|
||||
console.log("Randomizing track");
|
||||
/* Prevents anyone from changing the winning score after the round has started */
|
||||
if (this.roundStarted === false) {
|
||||
this.$store.dispatch("setRoundStarted", true);
|
||||
}
|
||||
let trackAddedToQue = false;
|
||||
this.disableButtons = true;
|
||||
let copyOfPlaylist = this.localPlaylist;
|
||||
setTimeout(() => this.isHidden = false, 2000);
|
||||
//If the local playlist of MP3s is empty, wait for a new track to load
|
||||
if (copyOfPlaylist.length === 0) {
|
||||
console.log("No track preloaded, loading a new one");
|
||||
if (this.lowPlayed) {
|
||||
await this.APIgetRandomizedLowTrack();
|
||||
} else {
|
||||
await this.APIgetRandomizedTrack();
|
||||
}
|
||||
if (this.addPlayed) {
|
||||
await this.APIaddPlayed();
|
||||
}
|
||||
await this.APIaddToQue();
|
||||
trackAddedToQue = true;
|
||||
}
|
||||
//Update the source file for the media player (binded property to Audio tag in the template)
|
||||
this.currentTrackSrcFile = window.URL.createObjectURL(copyOfPlaylist[0]);
|
||||
if (!trackAddedToQue) {
|
||||
if (this.addPlayed) {
|
||||
await this.APIaddPlayed();
|
||||
}
|
||||
await this.APIaddToQue();
|
||||
}
|
||||
await this.APIgetPlaylistHistory();
|
||||
|
||||
this.$refs.audioPlayer.play();
|
||||
this.$emit("scroll-to-bottom-playlist-history");
|
||||
copyOfPlaylist.shift();
|
||||
if (this.hideNextTrack) {
|
||||
this.$store.dispatch("setCurrentTrackHidden", true);
|
||||
} else {
|
||||
this.$store.dispatch("setCurrentTrackHidden", false);
|
||||
}
|
||||
await this.APIgetInfoAboutCurrentTrack();
|
||||
await this.randomizeTrackInAdvance();
|
||||
this.disableButtons = false;
|
||||
},
|
||||
async randomizeTrackInAdvance() {
|
||||
console.log("Randomizing track in advance");
|
||||
try {
|
||||
if (this.lowPlayed) {
|
||||
await this.APIgetRandomizedLowTrack();
|
||||
} else {
|
||||
await this.APIgetRandomizedTrack();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
console.log("Error: A track couldn't be loaded in advance");
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
async playSpecificTrack(track) {
|
||||
await this.APIplaySpecificTrack(track.SongNo);
|
||||
this.$store.dispatch("setCurrentlyLoadingTrack", "");
|
||||
await this.APIgetPlaylistHistory();
|
||||
this.$store.dispatch("setCurrentTrackHidden", false);
|
||||
await this.APIgetInfoAboutCurrentTrack();
|
||||
},
|
||||
showAnswer() {
|
||||
this.$store.dispatch("setCurrentTrackHidden", false);
|
||||
},
|
||||
startSoundTest() {
|
||||
this.$refs.audioPlayer.pause();
|
||||
this.currentTrackSrcFile = "sounds/check.mp3";
|
||||
this.$store.dispatch("setSpecialTrackIsPlaying", true);
|
||||
this.$nextTick(() => {
|
||||
this.$refs.audioPlayer.currentTime = 0;
|
||||
this.$refs.audioPlayer.play();
|
||||
});
|
||||
},
|
||||
playWelcomeSound() {
|
||||
let randomNumber = Math.floor(Math.random() * 10);
|
||||
this.$refs.audioPlayer.pause();
|
||||
if (randomNumber === 0) {
|
||||
this.currentTrackSrcFile = "sounds/sound1.mp3";
|
||||
} else if (randomNumber < 5) {
|
||||
this.currentTrackSrcFile = "sounds/intro_short.mp3";
|
||||
} else {
|
||||
this.currentTrackSrcFile = "sounds/intro_long.mp3";
|
||||
}
|
||||
this.$store.dispatch("setSpecialTrackIsPlaying", true);
|
||||
this.$nextTick(() => {
|
||||
this.$refs.audioPlayer.currentTime = 0;
|
||||
this.$refs.audioPlayer.play();
|
||||
});
|
||||
},
|
||||
playMatchSound() {
|
||||
this.$refs.audioPlayer.pause();
|
||||
this.currentTrackSrcFile = "sounds/sound0.mp3";
|
||||
this.$store.dispatch("setSpecialTrackIsPlaying", true);
|
||||
this.$nextTick(() => {
|
||||
this.$refs.audioPlayer.currentTime = 0;
|
||||
this.$refs.audioPlayer.play();
|
||||
});
|
||||
},
|
||||
playWinningSound() {
|
||||
this.$refs.audioPlayer.pause();
|
||||
this.currentTrackSrcFile = "sounds/winning.mp3";
|
||||
this.$store.dispatch("setSpecialTrackIsPlaying", true);
|
||||
this.$nextTick(() => {
|
||||
this.$refs.audioPlayer.currentTime = 0;
|
||||
this.$refs.audioPlayer.play();
|
||||
});
|
||||
},
|
||||
APIgetRandomizedTrack() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.axios({
|
||||
method: "get",
|
||||
url: `${arne.hostname}/music/rand`,
|
||||
responseType: "blob",
|
||||
onDownloadProgress: (progressEvent) => {
|
||||
let percentCompleted = Math.round(
|
||||
(progressEvent.loaded * 100) / progressEvent.total
|
||||
);
|
||||
if (percentCompleted % 10 === 0) {
|
||||
//Debug for slow downloading
|
||||
//console.log(percentCompleted);
|
||||
}
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
let mp3 = new Blob([response.data], { type: "audio/mp3" });
|
||||
this.$store.dispatch("updateLocalPlaylist", mp3);
|
||||
this.$nextTick(() => {
|
||||
resolve();
|
||||
});
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.log("An error happened when fetching the track");
|
||||
console.log(error);
|
||||
reject();
|
||||
});
|
||||
});
|
||||
},
|
||||
APIgetRandomizedLowTrack() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.axios({
|
||||
method: "get",
|
||||
url: `${arne.hostname}/music/rand/low`,
|
||||
responseType: "blob",
|
||||
onDownloadProgress: (progressEvent) => {
|
||||
let percentCompleted = Math.round(
|
||||
(progressEvent.loaded * 100) / progressEvent.total
|
||||
);
|
||||
if (percentCompleted % 10 === 0) {
|
||||
//Debug for slow downloading
|
||||
//console.log(percentCompleted);
|
||||
}
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
let mp3 = new Blob([response.data], { type: "audio/mp3" });
|
||||
this.$store.dispatch("updateLocalPlaylist", mp3);
|
||||
this.$nextTick(() => {
|
||||
resolve();
|
||||
});
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.log("An error happened when fetching the track");
|
||||
console.log(error);
|
||||
reject();
|
||||
});
|
||||
});
|
||||
},
|
||||
APIaddPlayed() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.axios({
|
||||
method: "get",
|
||||
url: `${arne.hostname}/music/addPlayed`,
|
||||
})
|
||||
.then(() => {
|
||||
resolve(false);
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.log(error);
|
||||
reject();
|
||||
});
|
||||
});
|
||||
},
|
||||
APIaddToQue() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.axios({
|
||||
method: "get",
|
||||
url: `${arne.hostname}/music/addQue`,
|
||||
})
|
||||
.then(() => {
|
||||
resolve(false);
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.log(error);
|
||||
reject();
|
||||
});
|
||||
});
|
||||
},
|
||||
APIgetInfoAboutCurrentTrack() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.axios({
|
||||
method: "get",
|
||||
url: `${arne.hostname}/music/info`,
|
||||
})
|
||||
.then((response) => {
|
||||
let gameInfoObject = {
|
||||
game: response.data.Game,
|
||||
track: response.data.Song.replace(".mp3", ""),
|
||||
songNo: response.data.SongNo,
|
||||
};
|
||||
this.$store.dispatch("setCurrentGame", gameInfoObject.game);
|
||||
this.$store.dispatch("setCurrentTrack", gameInfoObject.track);
|
||||
resolve(false);
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.log(error);
|
||||
reject();
|
||||
});
|
||||
});
|
||||
},
|
||||
APIgetPlaylistHistory() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.axios({
|
||||
method: "get",
|
||||
url: `${arne.hostname}/music/list`,
|
||||
})
|
||||
.then((response) => {
|
||||
let tracklistArray = response.data;
|
||||
this.$store.dispatch("updatePlaylistHistory", tracklistArray);
|
||||
resolve();
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.log(error);
|
||||
reject();
|
||||
});
|
||||
});
|
||||
},
|
||||
APIplaySpecificTrack(trackNumber) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.axios({
|
||||
method: "get",
|
||||
url: `${arne.hostname}/music?song=${trackNumber}`,
|
||||
responseType: "blob",
|
||||
})
|
||||
.then((response) => {
|
||||
let mp3 = new Blob([response.data], { type: "audio/mp3" });
|
||||
this.currentTrackSrcFile = window.URL.createObjectURL(mp3);
|
||||
this.$nextTick(() => {
|
||||
this.$refs.audioPlayer.play();
|
||||
resolve();
|
||||
});
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.log(error);
|
||||
reject();
|
||||
});
|
||||
});
|
||||
},
|
||||
trackHasEnded() {
|
||||
if (this.specialTrackIsPlaying) {
|
||||
this.$store.dispatch("setSpecialTrackIsPlaying", false);
|
||||
return 0;
|
||||
}
|
||||
if (!this.stopAfterCurrent) {
|
||||
this.randomizeTrack();
|
||||
}
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
await this.APIgetPlaylistHistory();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
footer {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.audioAndButtonsDiv {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
width: 80vw;
|
||||
margin-right: 1vh;
|
||||
}
|
||||
.audioButtonsDiv {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
width: 100%;
|
||||
margin-bottom: 2vh;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.audioButtonsDiv button:nth-child(1) {
|
||||
margin-right: 3vw;
|
||||
width: 200px;
|
||||
}
|
||||
.audioButtonsDiv button:nth-child(2) {
|
||||
margin-right: 25vw;
|
||||
width: 220px;
|
||||
}
|
||||
.audioButtonsDiv .bigButton {
|
||||
height: 7vh;
|
||||
}
|
||||
.disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.audioDiv {
|
||||
width: 100%;
|
||||
}
|
||||
.audioDiv audio {
|
||||
width: 100%;
|
||||
font-size: 2rem;
|
||||
}
|
||||
audio::-webkit-media-controls-panel {
|
||||
background-color: rgb(185, 185, 185);
|
||||
}
|
||||
audio::-webkit-media-controls-current-time-display {
|
||||
color: rgb(0, 0, 0);
|
||||
font-size: 4em;
|
||||
}
|
||||
audio::-webkit-media-controls-time-remaining-display {
|
||||
color: rgb(0, 0, 0);
|
||||
font-size: 4em;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1000px) {
|
||||
footer {
|
||||
position: static;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.audioAndButtonsDiv {
|
||||
width: 100vw;
|
||||
margin: 0;
|
||||
}
|
||||
.audioButtonsDiv {
|
||||
margin-bottom: 2vw;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.audioButtonsDiv button:nth-child(1) {
|
||||
margin: 0;
|
||||
width: 50vw;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.audioButtonsDiv button:nth-child(2) {
|
||||
margin: 0;
|
||||
width: 50vw;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.audioButtonsDiv .bigButton {
|
||||
height: 22vw;
|
||||
}
|
||||
.disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.audioDiv {
|
||||
width: 100%;
|
||||
}
|
||||
.audioDiv audio {
|
||||
font-size: 1rem;
|
||||
}
|
||||
audio::-webkit-media-controls-panel {
|
||||
background-color: rgb(185, 185, 185);
|
||||
border-radius: 2px;
|
||||
}
|
||||
audio::-webkit-media-controls-current-time-display {
|
||||
color: rgb(0, 0, 0);
|
||||
font-size: 2em;
|
||||
}
|
||||
audio::-webkit-media-controls-time-remaining-display {
|
||||
color: rgb(0, 0, 0);
|
||||
font-size: 2em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,139 +0,0 @@
|
||||
<template>
|
||||
<nav>
|
||||
<p class="title">Music Player Randomizer</p>
|
||||
<div class="navMenuWrapper">
|
||||
<button class="settingsButton" @click="showSettingsModal">
|
||||
Settings
|
||||
</button>
|
||||
<button class="statisticsButton" @click="showStatisticsModal">
|
||||
Statistics
|
||||
</button>
|
||||
<button class="aboutButton" @click="showAboutModal">About</button>
|
||||
</div>
|
||||
<p v-if="!$parent.displayWhenDesktop" class="mobileMenuButton" @click="showMobileMenu">
|
||||
Menu
|
||||
</p>
|
||||
<settingsModal ref="settingsModal"></settingsModal>
|
||||
<statisticsModal ref="statisticsModal"></statisticsModal>
|
||||
<aboutModal ref="aboutModal"></aboutModal>
|
||||
<mobileMenu
|
||||
ref="mobileMenu"
|
||||
@start-sound-test="startSoundTest"
|
||||
@show-settings-modal="showSettingsModal"
|
||||
@show-statistics-modal="showStatisticsModal"
|
||||
@show-about-modal="showAboutModal"
|
||||
></mobileMenu>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import aboutModal from "../items/aboutModal.vue";
|
||||
import statisticsModal from "../items/statisticsModal.vue";
|
||||
import settingsModal from "../items/settingsModal.vue";
|
||||
import mobileMenu from "../items/mobileMenu.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
aboutModal,
|
||||
settingsModal,
|
||||
statisticsModal,
|
||||
mobileMenu,
|
||||
},
|
||||
methods: {
|
||||
playSelectedTrack(track) {
|
||||
this.$refs.theFooter.playSpecificTrack(track);
|
||||
},
|
||||
scrollToBottomPlaylistHistory() {
|
||||
this.$refs.playlistHistory.scrollToBottom();
|
||||
},
|
||||
showAboutModal() {
|
||||
this.$refs.aboutModal.openModal();
|
||||
},
|
||||
showSettingsModal() {
|
||||
this.$refs.settingsModal.openModal();
|
||||
},
|
||||
showStatisticsModal() {
|
||||
this.$refs.statisticsModal.openModal();
|
||||
},
|
||||
showMobileMenu() {
|
||||
this.$refs.mobileMenu.openMobileMenu();
|
||||
this.showMobileMenuContent();
|
||||
},
|
||||
showMobileMenuContent() {
|
||||
setTimeout(() => {
|
||||
this.$refs.mobileMenu.openMobileMenuContent();
|
||||
}, 20);
|
||||
},
|
||||
startSoundTest() {
|
||||
this.$emit("start-sound-test");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
nav {
|
||||
background: #333;
|
||||
display: flex;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 5vh;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: #fff;
|
||||
font-size: 2rem;
|
||||
margin-left: 2vw;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.navMenuWrapper {
|
||||
margin-left: auto;
|
||||
height: 5vh;
|
||||
}
|
||||
|
||||
.navMenuWrapper button {
|
||||
background: #333;
|
||||
color: #ff9c00;
|
||||
font-size: 1.3rem;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
height: 100%;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.navMenuWrapper button:hover {
|
||||
background-color: #555;
|
||||
}
|
||||
|
||||
/* Mobile */
|
||||
@media only screen and (max-width: 1000px) {
|
||||
nav {
|
||||
justify-content: center;
|
||||
height: 10vw;
|
||||
}
|
||||
.title {
|
||||
font-size: 1.2rem;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
margin-left: 5px;
|
||||
/* text-align: ; */
|
||||
}
|
||||
|
||||
.navMenuWrapper {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobileMenuButton {
|
||||
color: white;
|
||||
background: grey;
|
||||
padding: 6px;
|
||||
border-top-left-radius: 7px;
|
||||
border-bottom-left-radius: 7px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,103 +0,0 @@
|
||||
<template>
|
||||
<div class="theMainDiv">
|
||||
<div class="mainFirstRow">
|
||||
<currently-playing></currently-playing>
|
||||
<inspiration-window></inspiration-window>
|
||||
</div>
|
||||
<div class="mainSecondRow">
|
||||
<playlist-history
|
||||
ref="playlistHistory"
|
||||
@play-selected-track="playSelectedTrack"
|
||||
></playlist-history>
|
||||
<players-window
|
||||
@play-welcome-sound="playWelcomeSound"
|
||||
@play-match-point-sound="playMatchSound"
|
||||
@play-winning-sound="playWinningSound"
|
||||
></players-window>
|
||||
</div>
|
||||
<extra-buttons
|
||||
v-if="$parent.displayWhenDesktop"
|
||||
@start-sound-test="startSoundTest"
|
||||
></extra-buttons>
|
||||
<the-footer
|
||||
ref="theFooter"
|
||||
@scroll-to-bottom-playlist-history="scrollToBottomPlaylistHistory"
|
||||
></the-footer>
|
||||
<!-- <the-footer ref="theFooter"></the-footer> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import currentlyPlaying from "../items/CurrentlyPlaying.vue";
|
||||
import inspirationWindow from "../items/InspirationWindow.vue";
|
||||
import playersWindow from "../items/playersWindow.vue";
|
||||
import playlistHistory from "../items/playlistHistory.vue";
|
||||
import extraButtons from "../items/extraButtons.vue";
|
||||
import theFooter from "./TheFooter.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
currentlyPlaying,
|
||||
playersWindow,
|
||||
playlistHistory,
|
||||
inspirationWindow,
|
||||
extraButtons,
|
||||
theFooter,
|
||||
},
|
||||
methods: {
|
||||
playSelectedTrack(track) {
|
||||
this.$refs.theFooter.playSpecificTrack(track);
|
||||
},
|
||||
scrollToBottomPlaylistHistory() {
|
||||
this.$refs.playlistHistory.scrollToBottom();
|
||||
},
|
||||
startSoundTest() {
|
||||
this.$refs.theFooter.startSoundTest();
|
||||
},
|
||||
playWelcomeSound() {
|
||||
this.$refs.theFooter.playWelcomeSound();
|
||||
},
|
||||
playMatchSound() {
|
||||
this.$refs.theFooter.playMatchSound();
|
||||
},
|
||||
playWinningSound() {
|
||||
this.$refs.theFooter.playWinningSound();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.theMainDiv {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mainFirstRow {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
width: 100%;
|
||||
margin: 0.5vw;
|
||||
}
|
||||
.mainSecondRow {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
width: 100%;
|
||||
margin: 0 0.5vw;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1000px) {
|
||||
.mainFirstRow {
|
||||
margin: 0;
|
||||
margin-top: 0.5vw;
|
||||
}
|
||||
.mainSecondRow {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
margin-top: 0.5vw;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,261 +0,0 @@
|
||||
import { createApp } from "vue";
|
||||
import { createStore } from "vuex";
|
||||
import App from "./App.vue";
|
||||
import axios from "axios";
|
||||
import VueAxios from "vue-axios";
|
||||
|
||||
const store = createStore({
|
||||
state() {
|
||||
return {
|
||||
currentGame: "",
|
||||
currentTrack: ``,
|
||||
currentTrackHidden: false,
|
||||
currentlyLoadingTrack: "",
|
||||
specialTrackIsPlaying: false,
|
||||
someoneHasWon: false,
|
||||
winningScore: 20,
|
||||
roundStarted: false,
|
||||
reloadGamesList: false,
|
||||
listOfPlayers: [],
|
||||
localPlaylist: [],
|
||||
playlistHistory: [
|
||||
{
|
||||
SongNo: "",
|
||||
Game: "",
|
||||
Song: "",
|
||||
},
|
||||
],
|
||||
/* Stats */
|
||||
howManyGames: 0,
|
||||
/* Options */
|
||||
stopAfterCurrent: true,
|
||||
hideNextTrack: true,
|
||||
addPlayed: true,
|
||||
lowPlayed: false,
|
||||
};
|
||||
},
|
||||
mutations: {
|
||||
setCurrentGame(state, payload) {
|
||||
state.currentGame = payload;
|
||||
},
|
||||
setCurrentTrack(state, payload) {
|
||||
state.currentTrack = payload;
|
||||
},
|
||||
setCurrentTrackHidden(state, payload) {
|
||||
state.currentTrackHidden = payload;
|
||||
},
|
||||
setCurrentlyLoadingTrack(state, payload) {
|
||||
state.currentlyLoadingTrack = payload;
|
||||
},
|
||||
setSpecialTrackIsPlaying(state, payload) {
|
||||
state.specialTrackIsPlaying = payload;
|
||||
},
|
||||
setSomeoneHasWon(state, payload) {
|
||||
state.someoneHasWon = payload;
|
||||
},
|
||||
setWinningScore(state, payload) {
|
||||
state.winningScore += payload;
|
||||
},
|
||||
setRoundStarted(state, payload) {
|
||||
state.roundStarted = payload;
|
||||
},
|
||||
updateListOfPlayers(state, payload) {
|
||||
state.listOfPlayers = payload;
|
||||
},
|
||||
changePlayerScore(state, payload) {
|
||||
state.listOfPlayers[payload.indexOfPlayer].score += payload.scoreToChange;
|
||||
},
|
||||
changePlayerWelcomed(state, payload) {
|
||||
state.listOfPlayers[payload.indexOfPlayer].welcomed = payload.playerWelcomeTrueOrFalse;
|
||||
},
|
||||
changePlayerMatchPoint(state, payload) {
|
||||
state.listOfPlayers[payload.indexOfPlayer].matchPoint = payload.playerMatchPointTrueOrFalse;
|
||||
},
|
||||
changePlayerProfile(state, payload) {
|
||||
state.listOfPlayers[payload.indexOfPlayer].profile = payload.profileSrc;
|
||||
},
|
||||
resetPlayerScore(state, payload) {
|
||||
state.listOfPlayers = payload;
|
||||
},
|
||||
resetPlayerWelcomed(state, payload) {
|
||||
state.listOfPlayers = payload;
|
||||
},
|
||||
resetPlayerMatchPoint(state, payload) {
|
||||
state.listOfPlayers = payload;
|
||||
},
|
||||
updateLocalPlaylist(state, payload) {
|
||||
state.localPlaylist.push(payload);
|
||||
},
|
||||
updatePlaylistHistory(state, payload) {
|
||||
state.playlistHistory = payload;
|
||||
},
|
||||
updateHowManyGames(state, payload) {
|
||||
state.howManyGames = payload;
|
||||
},
|
||||
reloadGamesList(state, payload) {
|
||||
state.reloadGamesList = payload;
|
||||
},
|
||||
updateStopAfterCurrent(state, payload) {
|
||||
state.stopAfterCurrent = payload;
|
||||
},
|
||||
updateHideNextTitle(state, payload) {
|
||||
state.hideNextTrack = payload;
|
||||
},
|
||||
updateAddPlayed(state, payload) {
|
||||
state.addPlayed = payload;
|
||||
},
|
||||
updateLowPlayed(state, payload) {
|
||||
state.lowPlayed = payload;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
setCurrentGame(context, payload) {
|
||||
context.commit("setCurrentGame", payload);
|
||||
},
|
||||
setCurrentTrack(context, payload) {
|
||||
context.commit("setCurrentTrack", payload);
|
||||
},
|
||||
setCurrentTrackHidden(context, payload) {
|
||||
context.commit("setCurrentTrackHidden", payload);
|
||||
},
|
||||
setCurrentlyLoadingTrack(context, payload) {
|
||||
context.commit("setCurrentlyLoadingTrack", payload);
|
||||
},
|
||||
setSpecialTrackIsPlaying(context, payload) {
|
||||
context.commit("setSpecialTrackIsPlaying", payload);
|
||||
},
|
||||
setSomeoneHasWon(context, payload) {
|
||||
context.commit("someoneHasWon", payload);
|
||||
},
|
||||
setWinningScore(context, payload) {
|
||||
context.commit("setWinningScore", payload);
|
||||
},
|
||||
setRoundStarted(context, payload) {
|
||||
context.commit("setRoundStarted", payload);
|
||||
},
|
||||
removePlayer(context, payload) {
|
||||
let newPlayerList = this.state.listOfPlayers.filter(
|
||||
(player) => player.playerName != payload
|
||||
);
|
||||
context.commit("updateListOfPlayers", newPlayerList);
|
||||
},
|
||||
addNewPlayer(context, payload) {
|
||||
let newPlayerList = this.state.listOfPlayers;
|
||||
let newPlayer = {
|
||||
playerName: payload,
|
||||
score: 0,
|
||||
welcomed: false,
|
||||
matchPoint: false,
|
||||
profile: "characters/noCharacter.png",
|
||||
};
|
||||
newPlayerList.push(newPlayer);
|
||||
context.commit("updateListOfPlayers", newPlayerList);
|
||||
},
|
||||
changePlayerScore(context, payload) {
|
||||
let copyOfPlayerList = this.state.listOfPlayers;
|
||||
let indexOfPlayer = copyOfPlayerList.findIndex(
|
||||
(player) => player.playerName === payload.playerName
|
||||
);
|
||||
let scoreToChange = payload.score;
|
||||
context.commit("changePlayerScore", {
|
||||
indexOfPlayer,
|
||||
scoreToChange,
|
||||
});
|
||||
},
|
||||
changePlayerWelcomed(context, payload) {
|
||||
let copyOfPlayerList = this.state.listOfPlayers;
|
||||
let indexOfPlayer = copyOfPlayerList.findIndex(
|
||||
(player) => player.playerName === payload.playerName
|
||||
);
|
||||
let playerWelcomeTrueOrFalse = payload.welcomeSet;
|
||||
context.commit("changePlayerWelcomed", {
|
||||
indexOfPlayer,
|
||||
playerWelcomeTrueOrFalse,
|
||||
});
|
||||
},
|
||||
changePlayerMatchPoint(context, payload) {
|
||||
let copyOfPlayerList = this.state.listOfPlayers;
|
||||
let indexOfPlayer = copyOfPlayerList.findIndex(
|
||||
(player) => player.playerName === payload.playerName
|
||||
);
|
||||
let playerMatchPointTrueOrFalse = payload.matchPointSet;
|
||||
context.commit("changePlayerMatchPoint", {
|
||||
indexOfPlayer,
|
||||
playerMatchPointTrueOrFalse,
|
||||
});
|
||||
},
|
||||
changePlayerProfile(context, payload) {
|
||||
let copyOfPlayerList = this.state.listOfPlayers;
|
||||
let indexOfPlayer = copyOfPlayerList.findIndex(
|
||||
(player) => player.playerName === payload.playerName
|
||||
);
|
||||
let profileSrc = payload.profile;
|
||||
context.commit("changePlayerProfile", {
|
||||
indexOfPlayer,
|
||||
profileSrc,
|
||||
});
|
||||
},
|
||||
resetPlayerScore(context) {
|
||||
/* The JSON.parse and JSON.stringify parts are used to make a copy that doesn't affect the original */
|
||||
let copyOfPlayerList = JSON.parse(JSON.stringify(this.state.listOfPlayers));
|
||||
for (let i = 0; i < copyOfPlayerList.length; i++) {
|
||||
copyOfPlayerList[i].score = 0;
|
||||
}
|
||||
context.commit("resetPlayerScore", copyOfPlayerList);
|
||||
},
|
||||
resetPlayerWelcomed(context) {
|
||||
/* The JSON.parse and JSON.stringify parts are used to make a copy that doesn't affect the original */
|
||||
let copyOfPlayerList = JSON.parse(JSON.stringify(this.state.listOfPlayers));
|
||||
for (let i = 0; i < copyOfPlayerList.length; i++) {
|
||||
copyOfPlayerList[i].welcomed = false;
|
||||
}
|
||||
context.commit("resetPlayerWelcomed", copyOfPlayerList);
|
||||
},
|
||||
resetPlayerMatchPoint(context) {
|
||||
/* The JSON.parse and JSON.stringify parts are used to make a copy that doesn't affect the original */
|
||||
let copyOfPlayerList = JSON.parse(JSON.stringify(this.state.listOfPlayers));
|
||||
for (let i = 0; i < copyOfPlayerList.length; i++) {
|
||||
copyOfPlayerList[i].matchPoint = false;
|
||||
}
|
||||
context.commit("resetPlayerMatchPoint", copyOfPlayerList);
|
||||
},
|
||||
updateLocalPlaylist(context, payload) {
|
||||
context.commit("updateLocalPlaylist", payload);
|
||||
},
|
||||
updatePlaylistHistory(context, payload) {
|
||||
context.commit("updatePlaylistHistory", payload);
|
||||
},
|
||||
updateHowManyGames(context, payload) {
|
||||
context.commit("updateHowManyGames", payload);
|
||||
},
|
||||
reloadGamesList(context, payload) {
|
||||
context.commit("reloadGamesList", payload);
|
||||
},
|
||||
updateStopAfterCurrent(context, payload) {
|
||||
context.commit("updateStopAfterCurrent", payload);
|
||||
},
|
||||
updateHideNextTitle(context, payload) {
|
||||
context.commit("updateHideNextTitle", payload);
|
||||
},
|
||||
updateAddPlayed(context, payload) {
|
||||
context.commit("updateAddPlayed", payload);
|
||||
},
|
||||
updateLowPlayed(context, payload) {
|
||||
context.commit("updateLowPlayed", payload);
|
||||
},
|
||||
},
|
||||
|
||||
getters: {
|
||||
/* Empty ATM */
|
||||
/* getListOfPlayers: (state) => {
|
||||
return state.listOfPlayers;
|
||||
}, */
|
||||
},
|
||||
});
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
app.use(store);
|
||||
app.use(VueAxios, axios);
|
||||
|
||||
app.mount("#app");
|
||||
@@ -2,84 +2,41 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"music-server/internal/logging"
|
||||
"music-server/internal/server"
|
||||
"net/http"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"music-server/internal/logging"
|
||||
"music-server/internal/server"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
//
|
||||
// @Title Swagger Example API
|
||||
// @version 0.5
|
||||
// @description This is a sample server Petstore server.
|
||||
// @termsOfService http://swagger.io/terms/
|
||||
const shutdownTimeout = 5 * time.Second
|
||||
|
||||
//
|
||||
// @contact.name Sebastian Olsson
|
||||
// @contact.email zarnor91@gmail.com
|
||||
|
||||
//
|
||||
// @license.name Apache 2.0
|
||||
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
// @host localhost:8080
|
||||
func main() {
|
||||
/*f, perr := os.Create("cpu.pprof")
|
||||
if perr != nil {
|
||||
log.Fatal(perr)
|
||||
}
|
||||
pprof.StartCPUProfile(f)
|
||||
defer pprof.StopCPUProfile()*/
|
||||
|
||||
appServer := server.NewServerInstance()
|
||||
httpServer := appServer.HTTPServer()
|
||||
|
||||
// Create a done channel to signal when the shutdown is complete
|
||||
done := make(chan bool, 1)
|
||||
|
||||
// Run graceful shutdown in a separate goroutine
|
||||
go gracefulShutdown(appServer, httpServer, done)
|
||||
|
||||
logging.GetLogger().Info("Server starting", zap.String("address", httpServer.Addr))
|
||||
err := httpServer.ListenAndServe()
|
||||
if err != nil && err != http.ErrServerClosed {
|
||||
logging.GetLogger().Fatal("HTTP server error", zap.String("error", err.Error()))
|
||||
}
|
||||
|
||||
// Wait for the graceful shutdown to complete
|
||||
<-done
|
||||
logging.GetLogger().Info("Graceful shutdown complete")
|
||||
}
|
||||
|
||||
func gracefulShutdown(appServer *server.Server, httpServer *http.Server, done chan bool) {
|
||||
// Create context that listens for the interrupt signal from the OS.
|
||||
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||
defer stop()
|
||||
|
||||
// Listen for the interrupt signal.
|
||||
<-ctx.Done()
|
||||
s := server.NewServerInstance()
|
||||
|
||||
logging.GetLogger().Info("Shutting down gracefully, press Ctrl+C again to force")
|
||||
serveErr := make(chan error, 1)
|
||||
go func() { serveErr <- s.ListenAndServe() }()
|
||||
|
||||
// Close database connection
|
||||
if appServer != nil && appServer.DB() != nil {
|
||||
appServer.DB().Close()
|
||||
select {
|
||||
case err := <-serveErr:
|
||||
if err != nil {
|
||||
logging.GetLogger().Fatal("HTTP server error", zap.Error(err))
|
||||
}
|
||||
case <-ctx.Done():
|
||||
stop()
|
||||
logging.GetLogger().Info("Shutting down gracefully, press Ctrl+C again to force")
|
||||
}
|
||||
|
||||
// The context is used to inform the server it has 5 seconds to finish
|
||||
// the request it is currently handling
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
|
||||
defer cancel()
|
||||
if err := httpServer.Shutdown(ctx); err != nil {
|
||||
logging.GetLogger().Error("Server forced to shutdown with error", zap.String("error", err.Error()))
|
||||
if err := s.Shutdown(shutdownCtx); err != nil {
|
||||
logging.GetLogger().Error("Server forced to shutdown with error", zap.Error(err))
|
||||
}
|
||||
|
||||
logging.GetLogger().Info("Server exiting")
|
||||
|
||||
// Notify the main goroutine that the shutdown is complete
|
||||
done <- true
|
||||
logging.GetLogger().Info("Graceful shutdown complete")
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Command openapi writes the MusicServer API description without starting the server.
|
||||
//
|
||||
// go run ./cmd/openapi -out .
|
||||
//
|
||||
// It writes openapi.json (OpenAPI 3.1) and openapi-3.0.json (OpenAPI 3.0, for tools
|
||||
// that don't support 3.1 yet). A running server serves the same files at /openapi.json
|
||||
// and /openapi-3.0.json.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"music-server/internal/server"
|
||||
)
|
||||
|
||||
func main() {
|
||||
out := flag.String("out", ".", "directory to write openapi.json and openapi-3.0.json to")
|
||||
flag.Parse()
|
||||
|
||||
if err := run(*out); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "openapi:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func run(out string) error {
|
||||
spec := server.OpenAPISpec()
|
||||
v31, err := spec.MarshalJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v30, err := spec.Downgrade()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(out, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
for name, data := range map[string][]byte{"openapi.json": v31, "openapi-3.0.json": v30} {
|
||||
path := filepath.Join(out, name)
|
||||
if err := os.WriteFile(path, data, 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("Wrote", path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRunWritesBothVersions(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
require.NoError(t, run(dir))
|
||||
|
||||
for name, version := range map[string]string{"openapi.json": "3.1.0", "openapi-3.0.json": "3.0.3"} {
|
||||
data, err := os.ReadFile(filepath.Join(dir, name))
|
||||
require.NoError(t, err)
|
||||
var spec struct {
|
||||
OpenAPI string `json:"openapi"`
|
||||
Paths map[string]any `json:"paths"`
|
||||
}
|
||||
require.NoError(t, json.Unmarshal(data, &spec), name)
|
||||
assert.Equal(t, version, spec.OpenAPI, name)
|
||||
assert.Contains(t, spec.Paths, "/api/v1/music/info", name)
|
||||
}
|
||||
}
|
||||
@@ -135,6 +135,12 @@ main {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.matched-name {
|
||||
margin: 0.25rem 0 0;
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 768px) {
|
||||
#search_term {
|
||||
|
||||
@@ -4,6 +4,3 @@ import "embed"
|
||||
|
||||
//go:embed "assets"
|
||||
var Assets embed.FS
|
||||
|
||||
//go:embed "swagger"
|
||||
var Swagger embed.FS
|
||||
@@ -1,77 +1,75 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"log"
|
||||
"music-server/internal/backend"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"music-server/internal/backend"
|
||||
"music-server/internal/logging"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var soundtracks_added []string
|
||||
// NewSearchHandler returns a handler that renders the soundtracks matching the posted search term.
|
||||
func NewSearchHandler(cache *backend.SearchCache) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
func FindSoundtrackWebHandler(w http.ResponseWriter, r *http.Request) {
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
soundtracks, err := cache.Get(r.Context())
|
||||
if err != nil {
|
||||
logging.GetLogger().Error("Failed to load search data", zap.Error(err))
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
search_term := r.FormValue("search_term")
|
||||
search(search_term)
|
||||
results := soundtracks_added
|
||||
|
||||
component := FoundSoundtracks(results)
|
||||
err = component.Render(r.Context(), w)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
log.Fatalf("Error rendering in FindSoundtrackWebHandler: %e", err)
|
||||
}
|
||||
results := search(r.FormValue("search_term"), soundtracks)
|
||||
if err := FoundSoundtracks(results).Render(r.Context(), w); err != nil {
|
||||
logging.GetLogger().Error("Failed to render search results", zap.Error(err))
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func search(searchText string) {
|
||||
soundtracks_added = nil
|
||||
soundtrackData := backend.GetSearchCache()
|
||||
results := searchWithData(searchText, soundtrackData)
|
||||
soundtracks_added = results
|
||||
// SearchResult is a soundtrack found by search. MatchedName is the alternative name that
|
||||
// matched, or empty when the soundtrack's own name matched.
|
||||
type SearchResult struct {
|
||||
Name string
|
||||
MatchedName string
|
||||
}
|
||||
|
||||
// searchWithData is a testable version of search that takes soundtrack data as a parameter
|
||||
func searchWithData(searchText string, soundtrackData []backend.SoundtrackData) []string {
|
||||
var results []string
|
||||
// search returns the soundtracks matching searchText: exact matches first, then matches
|
||||
// ignoring spaces and special characters, then matches with the letters in order. Each
|
||||
// step tries the soundtrack's own name before its alternative names.
|
||||
func search(searchText string, soundtrackData []backend.SoundtrackData) []SearchResult {
|
||||
var results []SearchResult
|
||||
seen := make(map[string]bool)
|
||||
|
||||
// Precompute cleaned and lowercase search terms once
|
||||
cleanedSearchTerm := strings.ToLower(searchText)
|
||||
cleanedSearchTerm = strings.ReplaceAll(cleanedSearchTerm, " ", "")
|
||||
cleanedSearchTerm = strings.ReplaceAll(cleanedSearchTerm, "é", "e")
|
||||
cleanedSearchTerm = strings.ReplaceAll(cleanedSearchTerm, "+", "plus")
|
||||
cleanedSearchTerm = strings.ReplaceAll(cleanedSearchTerm, "&", "and")
|
||||
cleanedSearchTerm = strings.ReplaceAll(cleanedSearchTerm, "'n", "and")
|
||||
lowercaseSearchText := strings.ToLower(searchText)
|
||||
|
||||
// Pass 1: Exact match (case-insensitive contains)
|
||||
for _, data := range soundtrackData {
|
||||
if !seen[data.Original] && (searchText == "" || strings.Contains(data.Lowercase, lowercaseSearchText)) {
|
||||
results = append(results, data.Original)
|
||||
seen[data.Original] = true
|
||||
add := func(match func(backend.SearchName) bool) {
|
||||
for _, data := range soundtrackData {
|
||||
if seen[data.Original] {
|
||||
continue
|
||||
}
|
||||
matched, ok := "", match(data.SearchName)
|
||||
for _, alternative := range data.Alternatives {
|
||||
if ok {
|
||||
break
|
||||
}
|
||||
matched, ok = alternative.Original, match(alternative)
|
||||
}
|
||||
if ok {
|
||||
results = append(results, SearchResult{Name: data.Original, MatchedName: matched})
|
||||
seen[data.Original] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 2: Contains match with cleaned terms
|
||||
for _, data := range soundtrackData {
|
||||
if !seen[data.Original] && (cleanedSearchTerm == "" || strings.Contains(data.Cleaned, cleanedSearchTerm)) {
|
||||
results = append(results, data.Original)
|
||||
seen[data.Original] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 3: Regex match with cleaned terms
|
||||
for _, data := range soundtrackData {
|
||||
if !seen[data.Original] && backend.MatchRegexCached(cleanedSearchTerm, data.Cleaned) {
|
||||
results = append(results, data.Original)
|
||||
seen[data.Original] = true
|
||||
}
|
||||
}
|
||||
lowercase := strings.ToLower(searchText)
|
||||
cleaned := backend.CleanSearchTerm(searchText)
|
||||
|
||||
add(func(n backend.SearchName) bool { return strings.Contains(n.Lowercase, lowercase) })
|
||||
add(func(n backend.SearchName) bool { return strings.Contains(n.Cleaned, cleaned) })
|
||||
add(func(n backend.SearchName) bool { return backend.MatchesSubsequence(cleaned, n.Cleaned) })
|
||||
return results
|
||||
}
|
||||
@@ -3,14 +3,12 @@ package web
|
||||
templ SearchForm() {
|
||||
@Base() {
|
||||
<button id="dark-mode-toggle">🌙</button>
|
||||
<input id="search_term" name="search_term" type="text" hx-post="/find" hx-trigger="keyup changed delay:0.25s" hx-target="#soundtracks-container"/>
|
||||
<input id="search_term" name="search_term" type="text" hx-post="/find" hx-trigger="load, keyup changed delay:0.25s" hx-target="#soundtracks-container"/>
|
||||
<button type="button" id="clear" name="clear">Clear</button>
|
||||
<div id="soundtracks-container"></div>
|
||||
<script>
|
||||
document.addEventListener('readystatechange', () => {
|
||||
if (document.readyState == 'complete') {
|
||||
// Initialize search
|
||||
htmlx.ajax('POST', '/find', '#soundtracks-container');
|
||||
document.getElementById("search_term").focus();
|
||||
|
||||
// Initialize theme from localStorage (default to dark)
|
||||
@@ -35,17 +33,20 @@ templ SearchForm() {
|
||||
|
||||
document.getElementById("clear").addEventListener("click", function (event) {
|
||||
document.getElementById("search_term").value = "";
|
||||
htmlx.ajax('POST', '/find', '#soundtracks-container');
|
||||
htmx.ajax('POST', '/find', '#soundtracks-container');
|
||||
document.getElementById("search_term").focus();
|
||||
});
|
||||
</script>
|
||||
}
|
||||
}
|
||||
|
||||
templ FoundSoundtracks(soundtracks []string) {
|
||||
templ FoundSoundtracks(soundtracks []SearchResult) {
|
||||
for _, soundtrack := range soundtracks {
|
||||
<div class="bg-green-100 p-4 shadow-md rounded-lg mt-6">
|
||||
<p class="soundtrack-text">{ soundtrack }</p>
|
||||
<p class="soundtrack-text">{ soundtrack.Name }</p>
|
||||
if soundtrack.MatchedName != "" {
|
||||
<p class="matched-name">Also known as { soundtrack.MatchedName }</p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -1,193 +1,143 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"music-server/internal/backend"
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"music-server/internal/backend"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestCleanTerm tests that search terms are cleaned correctly
|
||||
func TestCleanTerm(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
// CleanSearchTermForMatch removes spaces, replaces specific chars, and lowercases
|
||||
{"MGS4", "mgs4"},
|
||||
{"RE2", "re2"},
|
||||
{"FFVII", "ffvii"},
|
||||
{"Ys", "ys"},
|
||||
{" test ", "test"},
|
||||
{"Test 123", "test123"},
|
||||
{"A+B", "aplusb"},
|
||||
{"é", "e"},
|
||||
// Note: CleanSearchTermForMatch does NOT remove apostrophes, it only replaces "'n" with "and"
|
||||
{"It's", "it's"},
|
||||
// But it does replace "'n" with "and"
|
||||
{"I'n", "iand"},
|
||||
{"", ""},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
result := backend.CleanSearchTermForMatch(tt.input)
|
||||
if result != tt.expected {
|
||||
t.Errorf("CleanSearchTermForMatch(%q) = %q, want %q", tt.input, result, tt.expected)
|
||||
}
|
||||
}
|
||||
var soundtracks = []string{
|
||||
"Call of Duty: Modern Warfare",
|
||||
"Final Fantasy VII",
|
||||
"Joe & Mac",
|
||||
"Kingdom Hearts",
|
||||
"Lemmings (SNES + Mega Drive)",
|
||||
"MG",
|
||||
"Metal Gear Solid 4 - Guns of the Patriots",
|
||||
"Pokémon HeartGold & SoulSilver",
|
||||
"Resident Evil 2 (Remake)",
|
||||
"Ys III - Wanderers from Ys (+The Oath in Felghana)",
|
||||
}
|
||||
|
||||
// searchWithDataHelper is a helper to create backend.SoundtrackData from original names
|
||||
func searchWithDataHelper(originals []string) []backend.SoundtrackData {
|
||||
data := make([]backend.SoundtrackData, len(originals))
|
||||
for i, orig := range originals {
|
||||
data[i] = backend.SoundtrackData{
|
||||
Original: orig,
|
||||
Lowercase: strings.ToLower(orig),
|
||||
Cleaned: backend.CleanSearchTermForMatch(orig),
|
||||
func newCache(names []string, alternatives ...map[string][]string) *backend.SearchCache {
|
||||
soundtracks := make([]backend.SearchSoundtrack, len(names))
|
||||
for i, name := range names {
|
||||
soundtracks[i] = backend.SearchSoundtrack{Name: name}
|
||||
for _, a := range alternatives {
|
||||
soundtracks[i].AlternativeNames = a[name]
|
||||
}
|
||||
}
|
||||
return backend.NewSearchCache(func(context.Context) ([]backend.SearchSoundtrack, error) { return soundtracks, nil })
|
||||
}
|
||||
|
||||
// names returns the soundtrack names of results.
|
||||
func names(results []SearchResult) []string {
|
||||
var n []string
|
||||
for _, r := range results {
|
||||
n = append(n, r.Name)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func searchData(t *testing.T) []backend.SoundtrackData {
|
||||
data, err := newCache(soundtracks).Get(context.Background())
|
||||
require.NoError(t, err)
|
||||
return data
|
||||
}
|
||||
|
||||
// TestNormalSearch tests the normal search function
|
||||
func TestNormalSearch(t *testing.T) {
|
||||
// Set up comprehensive test data with all user-provided soundtracks
|
||||
testData := searchWithDataHelper([]string{
|
||||
"A Bird Story",
|
||||
"Actraiser",
|
||||
"Actraiser 2",
|
||||
"Bangai-O Spirits",
|
||||
"Ciconia When they Cry",
|
||||
"Civilization IV",
|
||||
"Civilization VI",
|
||||
"Clair Obscur - Expedition 33",
|
||||
"Club House Games (42 All-Time Classics)",
|
||||
"Final Fantasy Dissidia 012 Duodecim (Sista Fantasin Meningsskiljaktigheter 012 Tolv)",
|
||||
"Final Fantasy VII Crisis Core",
|
||||
"Final Fantasy VII Rebirth",
|
||||
"Final Fantasy VII Remake",
|
||||
"Final Fantasy VII",
|
||||
"Joe & Mac",
|
||||
"Judgment",
|
||||
"Kingdom Hearts",
|
||||
"Kingdom Hearts Birth By Sleep",
|
||||
"Kingdom Hearts II",
|
||||
"Kingdom Hearts III",
|
||||
"Lemmings (SNES + Mega Drive)",
|
||||
"Lemmings 3D",
|
||||
"Mega Man X4",
|
||||
"Metal Gear Rising - Revengeance",
|
||||
"Metal Gear Solid (+Twin Snakes)",
|
||||
"Metal Gear Solid 2 - Sons of Liberty",
|
||||
"Metal Gear Solid 3 - Snake Eater",
|
||||
"Metal Gear Solid 4 - Guns of the Patriots",
|
||||
"Metroid Prime",
|
||||
"Octopath Traveler",
|
||||
"Octopath Traveler 2",
|
||||
"Ori and the Blind Forest",
|
||||
"Ori and the Will of the Wisps",
|
||||
"OutRun 2006 - Coast 2 Coast",
|
||||
"Paper Mario - The Thousand Year Door",
|
||||
"Pokémon HeartGold & SoulSilver",
|
||||
"Resident Evil 2 (Remake)",
|
||||
"Shenmue",
|
||||
"Super Mario Galaxy",
|
||||
"Trails in the Sky FC (Sora no Kiseki FC)",
|
||||
"Trails in the Sky SC (Sora no Kiseki SC)",
|
||||
"Turrican II - The Final Fight",
|
||||
"Umineko no Naku Koro ni",
|
||||
"Umineko no Naku Koro ni Chiru",
|
||||
"VVVVVV",
|
||||
"YS VIII - Lacrimosa of DANA",
|
||||
"Yakuza 0",
|
||||
"Ys (Chronicles)",
|
||||
"Ys - Memories of Celceta",
|
||||
"Ys - The Ark of Napishtim",
|
||||
"Ys II (Chronicles)",
|
||||
"Ys III - Wanderers from Ys (+The Oath in Felghana)",
|
||||
"Ys IV - The Dawn of Ys",
|
||||
"Ys Origin",
|
||||
"Ys Seven",
|
||||
"Ys X Nordics",
|
||||
"Call of Duty: Modern Warfare",
|
||||
"Half-Life 3",
|
||||
})
|
||||
|
||||
// Test cases covering all match types
|
||||
func TestSearch(t *testing.T) {
|
||||
data := searchData(t)
|
||||
tests := []struct {
|
||||
query string
|
||||
wantFound string
|
||||
query string
|
||||
want []string
|
||||
}{
|
||||
// Exact substring matches
|
||||
{"Metal", "Metal Gear Solid 4 - Guns of the Patriots"},
|
||||
{"Resident", "Resident Evil 2 (Remake)"},
|
||||
{"Final", "Final Fantasy VII"},
|
||||
{"Ys", "Ys III - Wanderers from Ys (+The Oath in Felghana)"},
|
||||
|
||||
// Contains matches (on cleaned terms)
|
||||
{"gotp", "Metal Gear Solid 4 - Guns of the Patriots"},
|
||||
{"mgs", "Metal Gear Solid 4 - Guns of the Patriots"},
|
||||
{"kh", "Kingdom Hearts"},
|
||||
{"mg", "Metal Gear Solid 4 - Guns of the Patriots"},
|
||||
|
||||
// Regex matches
|
||||
{"mgs4go", "Metal Gear Solid 4 - Guns of the Patriots"},
|
||||
{"re2r", "Resident Evil 2 (Remake)"},
|
||||
{"ffv", "Final Fantasy VII"},
|
||||
{"yiwfyt", "Ys III - Wanderers from Ys (+The Oath in Felghana)"},
|
||||
{"codmw", "Call of Duty: Modern Warfare"},
|
||||
{"mgs4gop", "Metal Gear Solid 4 - Guns of the Patriots"},
|
||||
// Exact (case-insensitive) substring
|
||||
{"resident", []string{"Resident Evil 2 (Remake)"}},
|
||||
// Ignoring spaces and special characters
|
||||
{"joeandmac", []string{"Joe & Mac"}},
|
||||
{"snes+mega", []string{"Lemmings (SNES + Mega Drive)"}},
|
||||
{"pokemon", []string{"Pokémon HeartGold & SoulSilver"}},
|
||||
// Letters in order
|
||||
{"codmw", []string{"Call of Duty: Modern Warfare"}},
|
||||
{"mgs4gotp", []string{"Metal Gear Solid 4 - Guns of the Patriots"}},
|
||||
{"re2r", []string{"Resident Evil 2 (Remake)"}},
|
||||
// No match
|
||||
{"zzz", nil},
|
||||
// Regex special characters are plain text
|
||||
{"(remake)", []string{"Resident Evil 2 (Remake)"}},
|
||||
{"[", nil},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.query, func(t *testing.T) {
|
||||
results := searchWithData(tt.query, testData)
|
||||
|
||||
// Check that the expected item is in the results
|
||||
found := false
|
||||
for _, result := range results {
|
||||
if result == tt.wantFound {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("searchWithData(%q, ...) did not find expected item %q, got %v", tt.query, tt.wantFound, results)
|
||||
}
|
||||
assert.Equal(t, tt.want, names(search(tt.query, data)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestSearchOrdering tests that Pass 1 matches come before Pass 2 matches
|
||||
func TestSearchOrdering(t *testing.T) {
|
||||
// "MG" matches Pass 1 (exact substring on original)
|
||||
// "Metal Gear" matches Pass 2 (contains on cleaned) but NOT Pass 1 (no "mg" substring in "metal gear")
|
||||
testData := searchWithDataHelper([]string{
|
||||
"Metal Gear",
|
||||
"MG",
|
||||
})
|
||||
|
||||
results := searchWithData("mg", testData)
|
||||
|
||||
// Find indices
|
||||
indexPass1 := -1
|
||||
indexPass2 := -1
|
||||
for i, result := range results {
|
||||
if result == "MG" {
|
||||
indexPass1 = i
|
||||
}
|
||||
if result == "Metal Gear" {
|
||||
indexPass2 = i
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 1 match (MG) should come before Pass 2 match (Metal Gear)
|
||||
if indexPass1 >= 0 && indexPass2 >= 0 && indexPass1 >= indexPass2 {
|
||||
t.Errorf("Expected Pass 1 match 'MG' to come before Pass 2 match 'Metal Gear', but got order: %v", results)
|
||||
}
|
||||
func TestSearchEmptyReturnsEverything(t *testing.T) {
|
||||
assert.Equal(t, soundtracks, names(search("", searchData(t))))
|
||||
}
|
||||
|
||||
// Note: The tests above verify:
|
||||
// 1. clean_term - the term cleaning logic
|
||||
// 2. search - the complete normal search with exact, contains, and regex matching
|
||||
// 3. ordering - exact matches come before contains matches
|
||||
func TestSearchOrdering(t *testing.T) {
|
||||
// "MG" is an exact match, "Metal Gear Solid..." only matches by letters in order.
|
||||
results := names(search("mg", searchData(t)))
|
||||
require.GreaterOrEqual(t, len(results), 2)
|
||||
assert.Equal(t, "MG", results[0])
|
||||
assert.Contains(t, results, "Metal Gear Solid 4 - Guns of the Patriots")
|
||||
}
|
||||
|
||||
func TestSearchAlternativeNames(t *testing.T) {
|
||||
data, err := newCache(soundtracks, map[string][]string{
|
||||
"Final Fantasy VII": {"FF7", "ファイナルファンタジーVII"},
|
||||
"Kingdom Hearts": {"KH"},
|
||||
}).Get(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, []SearchResult{{Name: "Final Fantasy VII", MatchedName: "FF7"}}, search("ff7", data))
|
||||
assert.Equal(t, []SearchResult{{Name: "Final Fantasy VII", MatchedName: "ファイナルファンタジーVII"}}, search("ファイナル", data))
|
||||
assert.Equal(t, []SearchResult{{Name: "Final Fantasy VII"}}, search("fantasy", data),
|
||||
"no matched name when the soundtrack's own name matches")
|
||||
// An exact match on an alternative name comes before a letters-in-order match on a name.
|
||||
results := search("kh", data)
|
||||
require.NotEmpty(t, results)
|
||||
assert.Equal(t, SearchResult{Name: "Kingdom Hearts", MatchedName: "KH"}, results[0])
|
||||
}
|
||||
|
||||
func TestFoundSoundtracksShowsMatchedName(t *testing.T) {
|
||||
var out strings.Builder
|
||||
require.NoError(t, FoundSoundtracks([]SearchResult{
|
||||
{Name: "Final Fantasy VII", MatchedName: "FF7"}, {Name: "Kingdom Hearts"},
|
||||
}).Render(context.Background(), &out))
|
||||
assert.Contains(t, out.String(), "Final Fantasy VII")
|
||||
assert.Contains(t, out.String(), "Also known as FF7")
|
||||
assert.Equal(t, 1, strings.Count(out.String(), "Also known as"))
|
||||
}
|
||||
|
||||
func TestSearchHandler(t *testing.T) {
|
||||
handler := NewSearchHandler(newCache(soundtracks))
|
||||
req := httptest.NewRequest(http.MethodPost, "/find", strings.NewReader(url.Values{"search_term": {"kingdom"}}.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rec, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.Contains(t, rec.Body.String(), "Kingdom Hearts")
|
||||
assert.NotContains(t, rec.Body.String(), "Final Fantasy")
|
||||
}
|
||||
|
||||
func TestSearchHandlerLoadError(t *testing.T) {
|
||||
cache := backend.NewSearchCache(func(context.Context) ([]backend.SearchSoundtrack, error) { return nil, errors.New("db down") })
|
||||
rec := httptest.NewRecorder()
|
||||
NewSearchHandler(cache).ServeHTTP(rec, httptest.NewRequest(http.MethodPost, "/find", nil))
|
||||
assert.Equal(t, http.StatusInternalServerError, rec.Code)
|
||||
}
|
||||
|
Before Width: | Height: | Size: 665 B |
|
Before Width: | Height: | Size: 628 B |
@@ -1,60 +0,0 @@
|
||||
<!-- HTML for static distribution bundle build -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Swagger UI</title>
|
||||
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" />
|
||||
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
|
||||
<style>
|
||||
html
|
||||
{
|
||||
box-sizing: border-box;
|
||||
overflow: -moz-scrollbars-vertical;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after
|
||||
{
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body
|
||||
{
|
||||
margin:0;
|
||||
background: #fafafa;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
|
||||
<script src="./swagger-ui-bundle.js" charset="UTF-8"> </script>
|
||||
<script src="./swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
// Begin Swagger UI call region
|
||||
const ui = SwaggerUIBundle({
|
||||
url: "./swagger.yaml",
|
||||
dom_id: '#swagger-ui',
|
||||
deepLinking: true,
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIStandalonePreset
|
||||
],
|
||||
plugins: [
|
||||
SwaggerUIBundle.plugins.DownloadUrl
|
||||
],
|
||||
layout: "StandaloneLayout"
|
||||
});
|
||||
// End Swagger UI call region
|
||||
|
||||
window.ui = ui;
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,75 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<title>Swagger UI: OAuth2 Redirect</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
'use strict';
|
||||
function run () {
|
||||
var oauth2 = window.opener.swaggerUIRedirectOauth2;
|
||||
var sentState = oauth2.state;
|
||||
var redirectUrl = oauth2.redirectUrl;
|
||||
var isValid, qp, arr;
|
||||
|
||||
if (/code|token|error/.test(window.location.hash)) {
|
||||
qp = window.location.hash.substring(1);
|
||||
} else {
|
||||
qp = location.search.substring(1);
|
||||
}
|
||||
|
||||
arr = qp.split("&");
|
||||
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
|
||||
qp = qp ? JSON.parse('{' + arr.join() + '}',
|
||||
function (key, value) {
|
||||
return key === "" ? value : decodeURIComponent(value);
|
||||
}
|
||||
) : {};
|
||||
|
||||
isValid = qp.state === sentState;
|
||||
|
||||
if ((
|
||||
oauth2.auth.schema.get("flow") === "accessCode" ||
|
||||
oauth2.auth.schema.get("flow") === "authorizationCode" ||
|
||||
oauth2.auth.schema.get("flow") === "authorization_code"
|
||||
) && !oauth2.auth.code) {
|
||||
if (!isValid) {
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "warning",
|
||||
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
|
||||
});
|
||||
}
|
||||
|
||||
if (qp.code) {
|
||||
delete oauth2.state;
|
||||
oauth2.auth.code = qp.code;
|
||||
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
|
||||
} else {
|
||||
let oauthErrorMsg;
|
||||
if (qp.error) {
|
||||
oauthErrorMsg = "["+qp.error+"]: " +
|
||||
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
|
||||
(qp.error_uri ? "More info: "+qp.error_uri : "");
|
||||
}
|
||||
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "error",
|
||||
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
|
||||
});
|
||||
}
|
||||
} else {
|
||||
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
|
||||
}
|
||||
window.close();
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', function () {
|
||||
run();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,3 +0,0 @@
|
||||
/*! For license information please see swagger-ui-bundle.js.LICENSE.txt */
|
||||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.SwaggerUIBundle=t():e.SwaggerUIBundle=t()}(this,(function(){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist",n(n.s=479)}([function(e,t,n){"use strict";e.exports=n(555)},function(e,t,n){e.exports=function(){"use strict";var e=Array.prototype.slice;function t(e,t){t&&(e.prototype=Object.create(t.prototype)),e.prototype.constructor=e}function n(e){return i(e)?e:J(e)}function r(e){return u(e)?e:K(e)}function o(e){return s(e)?e:Y(e)}function a(e){return i(e)&&!c(e)?e:G(e)}function i(e){return!(!e||!e[f])}function u(e){return!(!e||!e[p])}function s(e){return!(!e||!e[h])}function c(e){return u(e)||s(e)}function l(e){return!(!e||!e[d])}t(r,n),t(o,n),t(a,n),n.isIterable=i,n.isKeyed=u,n.isIndexed=s,n.isAssociative=c,n.isOrdered=l,n.Keyed=r,n.Indexed=o,n.Set=a;var f="@@__IMMUTABLE_ITERABLE__@@",p="@@__IMMUTABLE_KEYED__@@",h="@@__IMMUTABLE_INDEXED__@@",d="@@__IMMUTABLE_ORDERED__@@",m="delete",v=5,g=1<<v,y=g-1,b={},w={value:!1},x={value:!1};function E(e){return e.value=!1,e}function _(e){e&&(e.value=!0)}function S(){}function k(e,t){t=t||0;for(var n=Math.max(0,e.length-t),r=new Array(n),o=0;o<n;o++)r[o]=e[o+t];return r}function A(e){return void 0===e.size&&(e.size=e.__iterate(C)),e.size}function O(e,t){if("number"!=typeof t){var n=t>>>0;if(""+n!==t||4294967295===n)return NaN;t=n}return t<0?A(e)+t:t}function C(){return!0}function j(e,t,n){return(0===e||void 0!==n&&e<=-n)&&(void 0===t||void 0!==n&&t>=n)}function T(e,t){return P(e,t,0)}function I(e,t){return P(e,t,t)}function P(e,t,n){return void 0===e?n:e<0?Math.max(0,t+e):void 0===t?e:Math.min(t,e)}var N=0,M=1,R=2,D="function"==typeof Symbol&&Symbol.iterator,L="@@iterator",B=D||L;function F(e){this.next=e}function z(e,t,n,r){var o=0===e?t:1===e?n:[t,n];return r?r.value=o:r={value:o,done:!1},r}function U(){return{value:void 0,done:!0}}function q(e){return!!H(e)}function V(e){return e&&"function"==typeof e.next}function W(e){var t=H(e);return t&&t.call(e)}function H(e){var t=e&&(D&&e[D]||e[L]);if("function"==typeof t)return t}function $(e){return e&&"number"==typeof e.length}function J(e){return null==e?ie():i(e)?e.toSeq():ce(e)}function K(e){return null==e?ie().toKeyedSeq():i(e)?u(e)?e.toSeq():e.fromEntrySeq():ue(e)}function Y(e){return null==e?ie():i(e)?u(e)?e.entrySeq():e.toIndexedSeq():se(e)}function G(e){return(null==e?ie():i(e)?u(e)?e.entrySeq():e:se(e)).toSetSeq()}F.prototype.toString=function(){return"[Iterator]"},F.KEYS=N,F.VALUES=M,F.ENTRIES=R,F.prototype.inspect=F.prototype.toSource=function(){return this.toString()},F.prototype[B]=function(){return this},t(J,n),J.of=function(){return J(arguments)},J.prototype.toSeq=function(){return this},J.prototype.toString=function(){return this.__toString("Seq {","}")},J.prototype.cacheResult=function(){return!this._cache&&this.__iterateUncached&&(this._cache=this.entrySeq().toArray(),this.size=this._cache.length),this},J.prototype.__iterate=function(e,t){return fe(this,e,t,!0)},J.prototype.__iterator=function(e,t){return pe(this,e,t,!0)},t(K,J),K.prototype.toKeyedSeq=function(){return this},t(Y,J),Y.of=function(){return Y(arguments)},Y.prototype.toIndexedSeq=function(){return this},Y.prototype.toString=function(){return this.__toString("Seq [","]")},Y.prototype.__iterate=function(e,t){return fe(this,e,t,!1)},Y.prototype.__iterator=function(e,t){return pe(this,e,t,!1)},t(G,J),G.of=function(){return G(arguments)},G.prototype.toSetSeq=function(){return this},J.isSeq=ae,J.Keyed=K,J.Set=G,J.Indexed=Y;var Q,Z,X,ee="@@__IMMUTABLE_SEQ__@@";function te(e){this._array=e,this.size=e.length}function ne(e){var t=Object.keys(e);this._object=e,this._keys=t,this.size=t.length}function re(e){this._iterable=e,this.size=e.length||e.size}function oe(e){this._iterator=e,this._iteratorCache=[]}function ae(e){return!(!e||!e[ee])}function ie(){return Q||(Q=new te([]))}function ue(e){var t=Array.isArray(e)?new te(e).fromEntrySeq():V(e)?new oe(e).fromEntrySeq():q(e)?new re(e).fromEntrySeq():"object"==typeof e?new ne(e):void 0;if(Line truncated
|
||||
//# sourceMappingURL=swagger-ui-bundle.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["webpack://SwaggerUIBundle/webpack/universalModuleDefinition","webpack://SwaggerUIBundle/webpack/bootstrap","webpack://SwaggerUIBundle/./node_modules/react/index.js","webpack://SwaggerUIBundle/./node_modules/immutable/dist/immutable.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/concat.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/defineProperty.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/map.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/classCallCheck.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/createClass.js","webpack://SwaggerUIBundle/./src/core/utils.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/inherits.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/createSuper.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/assertThisInitialized.js","webpack://SwaggerUIBundle/./node_modules/prop-types/index.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/filter.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/slicedToArray.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/toConsumableArray.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/object/keys.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/export.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/global.js","webpack://SwaggerUIBundle/./node_modules/reselect/es/defaultMemoize.js","webpack://SwaggerUIBundle/./node_modules/reselect/es/index.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/typeof.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/index-of.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/slice.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/array/is-array.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/for-each.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/object/assign.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/objectSpread2.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/extends.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/function-uncurry-this.js","webpack://SwaggerUIBundle/./src/core/window.js","webpack://SwaggerUIBundle/./node_modules/react-immutable-proptypes/dist/ImmutablePropTypes.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/json/stringify.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/reduce.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/includes.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/an-object.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/fails.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/path.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/object-is-prototype-of.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/function-call.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/is-pure.js","webpack://SwaggerUIBundle/./src/core/plugins/oas3/helpers.jsx","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/well-known-symbol.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/entry-virtual.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/is-callable.js","webpack://SwaggerUIBundle/./node_modules/lodash/get.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/regenerator/index.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/is-object.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/a-callable.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/has-own-property.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/define-well-known-symbol.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/iterate.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/bind.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/descriptors.js","webpack://SwaggerUIBundle/./src/core/plugins/spec/actions.js","webpack://SwaggerUIBundle/./node_modules/classnames/index.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/function-bind-context.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/find.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/hLine truncated
|
||||
@@ -1,3 +0,0 @@
|
||||
/*! For license information please see swagger-ui-es-bundle-core.js.LICENSE.txt */
|
||||
module.exports=function(e){var t={};function n(r){if(t[r])return t[r].exports;var a=t[r]={i:r,l:!1,exports:{}};return e[r].call(a.exports,a,a.exports,n),a.l=!0,a.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var a in e)n.d(r,a,function(t){return e[t]}.bind(null,a));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist",n(n.s=452)}([function(e,t){e.exports=require("react")},function(e,t){e.exports=require("immutable")},function(e,t,n){e.exports=n(492)},function(e,t,n){var r=n(178);e.exports=function(e,t,n){return t in e?r(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e},e.exports.__esModule=!0,e.exports.default=e.exports},function(e,t,n){e.exports=n(522)},function(e,t){e.exports=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},e.exports.__esModule=!0,e.exports.default=e.exports},function(e,t,n){var r=n(178);function a(e,t){for(var n=0;n<t.length;n++){var a=t[n];a.enumerable=a.enumerable||!1,a.configurable=!0,"value"in a&&(a.writable=!0),r(e,a.key,a)}}e.exports=function(e,t,n){return t&&a(e.prototype,t),n&&a(e,n),r(e,"prototype",{writable:!1}),e},e.exports.__esModule=!0,e.exports.default=e.exports},function(e,t,n){"use strict";(function(e){n.d(t,"z",(function(){return be})),n.d(t,"i",(function(){return Ee})),n.d(t,"v",(function(){return xe})),n.d(t,"r",(function(){return Se})),n.d(t,"t",(function(){return we})),n.d(t,"s",(function(){return je})),n.d(t,"p",(function(){return Oe})),n.d(t,"u",(function(){return Ce})),n.d(t,"x",(function(){return _e})),n.d(t,"y",(function(){return Ae})),n.d(t,"J",(function(){return ke})),n.d(t,"f",(function(){return Ie})),n.d(t,"n",(function(){return Pe})),n.d(t,"h",(function(){return Te})),n.d(t,"D",(function(){return Re})),n.d(t,"K",(function(){return Me})),n.d(t,"o",(function(){return ze})),n.d(t,"C",(function(){return Ve})),n.d(t,"a",(function(){return Fe})),n.d(t,"H",(function(){return Je})),n.d(t,"b",(function(){return We})),n.d(t,"G",(function(){return He})),n.d(t,"F",(function(){return $e})),n.d(t,"E",(function(){return Ke})),n.d(t,"k",(function(){return Ye})),n.d(t,"d",(function(){return Ge})),n.d(t,"g",(function(){return Ze})),n.d(t,"m",(function(){return Xe})),n.d(t,"l",(function(){return Qe})),n.d(t,"e",(function(){return et})),n.d(t,"I",(function(){return tt})),n.d(t,"w",(function(){return nt})),n.d(t,"A",(function(){return rt})),n.d(t,"B",(function(){return at})),n.d(t,"j",(function(){return ot})),n.d(t,"c",(function(){return it})),n.d(t,"q",(function(){return ct}));var r=n(14),a=n.n(r),o=(n(13),n(19)),i=n.n(o),s=n(56),c=n.n(s),u=n(22),l=n.n(u),p=n(4),f=n.n(p),d=n(83),h=n.n(d),m=n(2),v=n.n(m),g=n(23),y=n.n(g),b=n(12),E=n.n(b),x=n(15),S=n.n(x),w=(n(50),n(31)),j=n.n(w),O=n(24),C=n.n(O),_=n(194),A=n.n(_),k=n(21),I=n.n(k),P=n(74),T=n.n(P),R=(n(32),n(30)),N=n.n(R),M=n(20),q=n.n(M),D=n(55),B=n.n(D),U=n(106),L=n.n(U),z=n(104),V=n.n(z),F=n(1),J=n.n(F),W=n(418),H=n(419),$=n.n(H),K=n(244),Y=n.n(K),G=n(107),Z=n.n(G),X=n(420),Q=n.n(X),ee=n(306),te=n.n(ee),ne=n(102),re=n.n(ne),ae=n(103),oe=n.n(ae),ie=n(133),se=n(28),ce=n(424),ue=n.n(ce),le=n(138),pe=n(117),fe=n.n(pe),de=n(425),he=n.n(de),me=n(73),ve=n.n(me),ge="default",ye=function(e){return J.a.Iterable.isIterable(e)};function be(e){return we(e)?ye(e)?e.toJS():e:{}}function Ee(e){var t,n;if(ye(e))return e;if(e instanceof se.a.File)return e;if(!we(e))return e;if(l()(e))return f()(n=J.a.Seq(e)).call(n,Ee).toList();if(oe()(h()(e))){var r,a=function(e){if(!oe()(h()(e)))return e;var t,n={},r="_**[]",a={},o=c()(h()(e).call(e));try{for(o.s();!(t=o.n()).done;){var i=t.value;if(n[i[0]]||a[i[0]]&&a[i[0]].containsMultiple){var s,u,l,p;if(!a[i[0]])a[i[0]]={containsMultiple:!0,length:1},n[v()(l=v()(p="".concat(i[0])).call(p,r)).call(l,a[i[0]].length)]=n[i[0]],delete n[i[0]];a[i[0]].length+=1,n[v()(s=v()(u="".concat(i[0])).call(u,r)).call(s,a[i[0]].length)]=i[1]}else n[i[0]]=i[1]}}catch(e){o.e(e)}finally{o.f()}return n}(e);return f()(r=J.a.OrderedMap(a)).call(r,Ee)}return f()(t=J.a.OrderedMap(e)).call(t,Ee)}function xe(e){return l()(e)?e:[e]}function Se(e){return"function"==typeof e}function we(e){return!!e&&"object"===i()(e)}function je(e){return"function"==typeof e}function Oe(e){return l()(e)}var Ce=Z.a;function _e(e,t){var n;return j()(n=S()(e)).call(n,(function(n,r){return n[r]=t(e[r],r),n}),{})}function Ae(e,t){var n;return j()(n=S()(e)).call(n,(function(n,r){var a=t(e[r],r);retuLine truncated
|
||||
//# sourceMappingURL=swagger-ui-es-bundle-core.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["webpack://SwaggerUIBundle/webpack/bootstrap","webpack://SwaggerUIBundle/external \"react\"","webpack://SwaggerUIBundle/external \"immutable\"","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/concat.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/defineProperty.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/map.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/classCallCheck.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/createClass.js","webpack://SwaggerUIBundle/./src/core/utils.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/inherits.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/createSuper.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/assertThisInitialized.js","webpack://SwaggerUIBundle/external \"prop-types\"","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/filter.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/slicedToArray.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/toConsumableArray.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/object/keys.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/export.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/global.js","webpack://SwaggerUIBundle/external \"reselect\"","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/typeof.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/index-of.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/slice.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/array/is-array.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/for-each.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/object/assign.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/objectSpread2.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/extends.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/function-uncurry-this.js","webpack://SwaggerUIBundle/./src/core/window.js","webpack://SwaggerUIBundle/external \"react-immutable-proptypes\"","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/json/stringify.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/reduce.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/includes.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/an-object.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/fails.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/path.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/object-is-prototype-of.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/function-call.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/is-pure.js","webpack://SwaggerUIBundle/./src/core/plugins/oas3/helpers.jsx","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/well-known-symbol.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/entry-virtual.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/is-callable.js","webpack://SwaggerUIBundle/./node_modules/lodash/get.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/regenerator/index.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/is-object.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/a-callable.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/has-own-property.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/define-well-known-symbol.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/iterate.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/bind.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/descriptors.js","webpack://SwaggerUIBundle/./src/core/plugins/spec/actions.js","webpack://SwaggerUIBundle/external \"classnames\"","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/function-bind-context.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/find.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/createForOfIteratorHelper.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/get-built-in.js","webpack://SwaggerUIBundle/./node_modules/lodash/isArray.js","webpack://SwaggerUIBundle/./node_modules/@babel/ruLine truncated
|
||||
@@ -1,3 +0,0 @@
|
||||
/*! For license information please see swagger-ui-es-bundle.js.LICENSE.txt */
|
||||
module.exports=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/dist",n(n.s=479)}([function(e,t,n){"use strict";e.exports=n(555)},function(e,t,n){e.exports=function(){"use strict";var e=Array.prototype.slice;function t(e,t){t&&(e.prototype=Object.create(t.prototype)),e.prototype.constructor=e}function n(e){return i(e)?e:J(e)}function r(e){return u(e)?e:K(e)}function o(e){return s(e)?e:Y(e)}function a(e){return i(e)&&!c(e)?e:G(e)}function i(e){return!(!e||!e[f])}function u(e){return!(!e||!e[p])}function s(e){return!(!e||!e[h])}function c(e){return u(e)||s(e)}function l(e){return!(!e||!e[d])}t(r,n),t(o,n),t(a,n),n.isIterable=i,n.isKeyed=u,n.isIndexed=s,n.isAssociative=c,n.isOrdered=l,n.Keyed=r,n.Indexed=o,n.Set=a;var f="@@__IMMUTABLE_ITERABLE__@@",p="@@__IMMUTABLE_KEYED__@@",h="@@__IMMUTABLE_INDEXED__@@",d="@@__IMMUTABLE_ORDERED__@@",m="delete",v=5,g=1<<v,y=g-1,b={},w={value:!1},x={value:!1};function E(e){return e.value=!1,e}function _(e){e&&(e.value=!0)}function S(){}function k(e,t){t=t||0;for(var n=Math.max(0,e.length-t),r=new Array(n),o=0;o<n;o++)r[o]=e[o+t];return r}function A(e){return void 0===e.size&&(e.size=e.__iterate(C)),e.size}function O(e,t){if("number"!=typeof t){var n=t>>>0;if(""+n!==t||4294967295===n)return NaN;t=n}return t<0?A(e)+t:t}function C(){return!0}function j(e,t,n){return(0===e||void 0!==n&&e<=-n)&&(void 0===t||void 0!==n&&t>=n)}function T(e,t){return P(e,t,0)}function I(e,t){return P(e,t,t)}function P(e,t,n){return void 0===e?n:e<0?Math.max(0,t+e):void 0===t?e:Math.min(t,e)}var N=0,M=1,R=2,D="function"==typeof Symbol&&Symbol.iterator,L="@@iterator",B=D||L;function F(e){this.next=e}function z(e,t,n,r){var o=0===e?t:1===e?n:[t,n];return r?r.value=o:r={value:o,done:!1},r}function U(){return{value:void 0,done:!0}}function q(e){return!!H(e)}function V(e){return e&&"function"==typeof e.next}function W(e){var t=H(e);return t&&t.call(e)}function H(e){var t=e&&(D&&e[D]||e[L]);if("function"==typeof t)return t}function $(e){return e&&"number"==typeof e.length}function J(e){return null==e?ie():i(e)?e.toSeq():ce(e)}function K(e){return null==e?ie().toKeyedSeq():i(e)?u(e)?e.toSeq():e.fromEntrySeq():ue(e)}function Y(e){return null==e?ie():i(e)?u(e)?e.entrySeq():e.toIndexedSeq():se(e)}function G(e){return(null==e?ie():i(e)?u(e)?e.entrySeq():e:se(e)).toSetSeq()}F.prototype.toString=function(){return"[Iterator]"},F.KEYS=N,F.VALUES=M,F.ENTRIES=R,F.prototype.inspect=F.prototype.toSource=function(){return this.toString()},F.prototype[B]=function(){return this},t(J,n),J.of=function(){return J(arguments)},J.prototype.toSeq=function(){return this},J.prototype.toString=function(){return this.__toString("Seq {","}")},J.prototype.cacheResult=function(){return!this._cache&&this.__iterateUncached&&(this._cache=this.entrySeq().toArray(),this.size=this._cache.length),this},J.prototype.__iterate=function(e,t){return fe(this,e,t,!0)},J.prototype.__iterator=function(e,t){return pe(this,e,t,!0)},t(K,J),K.prototype.toKeyedSeq=function(){return this},t(Y,J),Y.of=function(){return Y(arguments)},Y.prototype.toIndexedSeq=function(){return this},Y.prototype.toString=function(){return this.__toString("Seq [","]")},Y.prototype.__iterate=function(e,t){return fe(this,e,t,!1)},Y.prototype.__iterator=function(e,t){return pe(this,e,t,!1)},t(G,J),G.of=function(){return G(arguments)},G.prototype.toSetSeq=function(){return this},J.isSeq=ae,J.Keyed=K,J.Set=G,J.Indexed=Y;var Q,Z,X,ee="@@__IMMUTABLE_SEQ__@@";function te(e){this._array=e,this.size=e.length}function ne(e){var t=Object.keys(e);this._object=e,this._keys=t,this.size=t.length}function re(e){this._iterable=e,this.size=e.length||e.size}function oe(e){this._iterator=e,this._iteratorCache=[]}function ae(e){return!(!e||!e[ee])}function ie(){return Q||(Q=new te([]))}function ue(e){var t=Array.isArray(e)?new te(e).fromEntrySeq():V(e)?new oe(e).fromEntrySeq():q(e)?new re(e).fromEntrySeq():"object"==typeof e?new ne(e):void 0;if(!t)throw new TypeError("Expected Array or iterable object of [k, v] entries, or keyed object: "+e);return t}function se(e){var t=le(e);if(!t)throw new TypeError("Expected Array or iterable object of values: "+e);return tLine truncated
|
||||
//# sourceMappingURL=swagger-ui-es-bundle.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["webpack://SwaggerUIBundle/webpack/bootstrap","webpack://SwaggerUIBundle/./node_modules/react/index.js","webpack://SwaggerUIBundle/./node_modules/immutable/dist/immutable.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/concat.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/defineProperty.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/map.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/classCallCheck.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/createClass.js","webpack://SwaggerUIBundle/./src/core/utils.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/inherits.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/createSuper.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/assertThisInitialized.js","webpack://SwaggerUIBundle/./node_modules/prop-types/index.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/filter.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/slicedToArray.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/toConsumableArray.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/object/keys.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/export.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/global.js","webpack://SwaggerUIBundle/./node_modules/reselect/es/defaultMemoize.js","webpack://SwaggerUIBundle/./node_modules/reselect/es/index.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/typeof.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/index-of.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/slice.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/array/is-array.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/for-each.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/object/assign.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/objectSpread2.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/extends.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/function-uncurry-this.js","webpack://SwaggerUIBundle/./src/core/window.js","webpack://SwaggerUIBundle/./node_modules/react-immutable-proptypes/dist/ImmutablePropTypes.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/json/stringify.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/reduce.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/includes.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/an-object.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/fails.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/path.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/object-is-prototype-of.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/function-call.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/is-pure.js","webpack://SwaggerUIBundle/./src/core/plugins/oas3/helpers.jsx","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/well-known-symbol.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/entry-virtual.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/is-callable.js","webpack://SwaggerUIBundle/./node_modules/lodash/get.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/regenerator/index.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/is-object.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/a-callable.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/has-own-property.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/define-well-known-symbol.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/iterate.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/bind.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/descriptors.js","webpack://SwaggerUIBundle/./src/core/plugins/spec/actions.js","webpack://SwaggerUIBundle/./node_modules/classnames/index.js","webpack://SwaggerUIBundle/./node_modules/core-js-pure/internals/function-bind-context.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/find.js","webpack://SwaggerUIBundle/./node_modules/@babel/runtime-corejs3/helpers/createForOfIteratorHelper.js","webpack://SwaggerUIBundlLine truncated
|
||||
@@ -1,3 +0,0 @@
|
||||
/*! For license information please see swagger-ui-standalone-preset.js.LICENSE.txt */
|
||||
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.SwaggerUIStandalonePreset=e():t.SwaggerUIStandalonePreset=e()}(this,(function(){return function(t){var e={};function n(r){if(e[r])return e[r].exports;var i=e[r]={i:r,l:!1,exports:{}};return t[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var i in t)n.d(r,i,function(e){return t[e]}.bind(null,i));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="/dist",n(n.s=247)}([function(t,e,n){"use strict";var r=n(1),i=n(72),o=n(6),u=n(15),s=n(74).f,a=n(256),c=n(16),f=n(25),l=n(51),p=n(20),h=function(t){var e=function(n,r,o){if(this instanceof e){switch(arguments.length){case 0:return new t;case 1:return new t(n);case 2:return new t(n,r)}return new t(n,r,o)}return i(t,this,arguments)};return e.prototype=t.prototype,e};t.exports=function(t,e){var n,i,d,y,v,g,w,M,_=t.target,L=t.global,x=t.stat,m=t.proto,j=L?r:x?r[_]:(r[_]||{}).prototype,b=L?c:c[_]||l(c,_,{})[_],N=b.prototype;for(d in e)n=!a(L?d:_+(x?".":"#")+d,t.forced)&&j&&p(j,d),v=b[d],n&&(g=t.noTargetGet?(M=s(j,d))&&M.value:j[d]),y=n&&g?g:e[d],n&&typeof v==typeof y||(w=t.bind&&n?f(y,r):t.wrap&&n?h(y):m&&u(y)?o(y):y,(t.sham||y&&y.sham||v&&v.sham)&&l(w,"sham",!0),l(b,d,w),m&&(p(c,i=_+"Prototype")||l(c,i,{}),l(c[i],d,y),t.real&&N&&!N[d]&&l(N,d,y)))}},function(t,e,n){(function(e){var n=function(t){return t&&t.Math==Math&&t};t.exports=n("object"==typeof globalThis&&globalThis)||n("object"==typeof window&&window)||n("object"==typeof self&&self)||n("object"==typeof e&&e)||function(){return this}()||Function("return this")()}).call(this,n(33))},function(t,e,n){"use strict";t.exports=n(320)},function(t,e,n){t.exports=function(){"use strict";var t=Array.prototype.slice;function e(t,e){e&&(t.prototype=Object.create(e.prototype)),t.prototype.constructor=t}function n(t){return u(t)?t:J(t)}function r(t){return s(t)?t:Z(t)}function i(t){return a(t)?t:X(t)}function o(t){return u(t)&&!c(t)?t:V(t)}function u(t){return!(!t||!t[l])}function s(t){return!(!t||!t[p])}function a(t){return!(!t||!t[h])}function c(t){return s(t)||a(t)}function f(t){return!(!t||!t[d])}e(r,n),e(i,n),e(o,n),n.isIterable=u,n.isKeyed=s,n.isIndexed=a,n.isAssociative=c,n.isOrdered=f,n.Keyed=r,n.Indexed=i,n.Set=o;var l="@@__IMMUTABLE_ITERABLE__@@",p="@@__IMMUTABLE_KEYED__@@",h="@@__IMMUTABLE_INDEXED__@@",d="@@__IMMUTABLE_ORDERED__@@",y="delete",v=5,g=1<<v,w=g-1,M={},_={value:!1},L={value:!1};function x(t){return t.value=!1,t}function m(t){t&&(t.value=!0)}function j(){}function b(t,e){e=e||0;for(var n=Math.max(0,t.length-e),r=new Array(n),i=0;i<n;i++)r[i]=t[i+e];return r}function N(t){return void 0===t.size&&(t.size=t.__iterate(D)),t.size}function S(t,e){if("number"!=typeof e){var n=e>>>0;if(""+n!==e||4294967295===n)return NaN;e=n}return e<0?N(t)+e:e}function D(){return!0}function I(t,e,n){return(0===t||void 0!==n&&t<=-n)&&(void 0===e||void 0!==n&&e>=n)}function A(t,e){return C(t,e,0)}function T(t,e){return C(t,e,e)}function C(t,e,n){return void 0===t?n:t<0?Math.max(0,e+t):void 0===e?t:Math.min(e,t)}var E=0,O=1,z=2,k="function"==typeof Symbol&&Symbol.iterator,U="@@iterator",Y=k||U;function R(t){this.next=t}function P(t,e,n,r){var i=0===t?e:1===t?n:[e,n];return r?r.value=i:r={value:i,done:!1},r}function Q(){return{value:void 0,done:!0}}function B(t){return!!W(t)}function F(t){return t&&"function"==typeof t.next}function G(t){var e=W(t);return e&&e.call(t)}function W(t){var e=t&&(k&&t[k]||t[U]);if("function"==typeof e)return e}function q(t){return t&&"number"==typeof t.length}function J(t){return null==t?ut():u(t)?t.toSeq():ct(t)}function Z(t){return null==t?ut().toKeyedSeq():u(t)?s(t)?t.toSeq():t.fromEntrySeq():st(t)}function X(t){return null==t?ut():u(t)?s(t)?t.entrySeq():t.toIndexedSeq():at(t)}function V(t){return(null==t?ut():u(t)?s(t)?t.entrySeq():t:at(t)).toSetSeq()}R.prototype.toString=function(){return"[Iterator]"},R.KEYS=E,R.VALUES=O,R.ENTRIES=z,R.prototype.inspect=R.prototype.toSource=function(){return this.toString()},R.prototype[Y]=function(){return this},e(J,n),J.of=function(){return J(arguments)},J.prototype.toSeq=function(){return this},J.prototype.toString=function(){return this.__toString("Seq {","}")},J.prototype.cacheResult=function(){return!this._cache&&this.__iterateUncached&&(this._cache=this.entrySeq().Line truncated
|
||||
//# sourceMappingURL=swagger-ui-standalone-preset.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["webpack://SwaggerUIStandalonePreset/webpack/universalModuleDefinition","webpack://SwaggerUIStandalonePreset/webpack/bootstrap","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/export.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/global.js","webpack://SwaggerUIStandalonePreset/./node_modules/react/index.js","webpack://SwaggerUIStandalonePreset/./node_modules/immutable/dist/immutable.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/an-object.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/fails.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/function-uncurry-this.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/concat.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/core-js-stable/array/is-array.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/object-is-prototype-of.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/is-pure.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/function-call.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/define-well-known-symbol.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/well-known-symbol.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/entry-virtual.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/is-callable.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/path.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/a-callable.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/descriptors.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/is-object.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/has-own-property.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/iterate.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/for-each.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/map.js","webpack://SwaggerUIStandalonePreset/./src/core/utils.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/function-bind-context.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/helpers/defineProperty.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/to-object.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/object-define-property.js","webpack://SwaggerUIStandalonePreset/./node_modules/inherits/inherits_browser.js","webpack://SwaggerUIStandalonePreset/./node_modules/safe-buffer/index.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/helpers/typeof.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/get-built-in.js","webpack://SwaggerUIStandalonePreset/(webpack)/buildin/global.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/length-of-array-like.js","webpack://SwaggerUIStandalonePreset/./node_modules/lodash/isArray.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/get-map-iterator.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/helpers/classCallCheck.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/helpers/createClass.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/helpers/inherits.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/helpers/createSuper.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/to-indexed-object.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/array-iteration.js","webpack://SwaggerUIStandalonePreset/./node_modules/lodash/_root.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/helpers/assertThisInitialized.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/core-js-stable/instance/filter.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/core-js-stable/object/keys.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/classof.js","webpack://SwaggerUIStandalonePreset/./node_modules/lodash/memoize.js","webpack://SwaggerUIStandalonePreset/./node_modules/@babel/runtime-corejs3/helpers/toConsumableArray.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/classof-raw.js","webpack://SwaggerUIStandalonePreset/./node_modules/core-js-pure/internals/create-non-enumerable-property.js","webpack://SwaggerUIStandalonePresetLine truncated
|
||||
@@ -1,4 +0,0 @@
|
||||
.swagger-ui{color:#3b4151;
|
||||
/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */font-family:sans-serif}.swagger-ui html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;line-height:1.15}.swagger-ui body{margin:0}.swagger-ui article,.swagger-ui aside,.swagger-ui footer,.swagger-ui header,.swagger-ui nav,.swagger-ui section{display:block}.swagger-ui h1{font-size:2em;margin:.67em 0}.swagger-ui figcaption,.swagger-ui figure,.swagger-ui main{display:block}.swagger-ui figure{margin:1em 40px}.swagger-ui hr{box-sizing:content-box;height:0;overflow:visible}.swagger-ui pre{font-family:monospace,monospace;font-size:1em}.swagger-ui a{-webkit-text-decoration-skip:objects;background-color:transparent}.swagger-ui abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}.swagger-ui b,.swagger-ui strong{font-weight:inherit;font-weight:bolder}.swagger-ui code,.swagger-ui kbd,.swagger-ui samp{font-family:monospace,monospace;font-size:1em}.swagger-ui dfn{font-style:italic}.swagger-ui mark{background-color:#ff0;color:#000}.swagger-ui small{font-size:80%}.swagger-ui sub,.swagger-ui sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}.swagger-ui sub{bottom:-.25em}.swagger-ui sup{top:-.5em}.swagger-ui audio,.swagger-ui video{display:inline-block}.swagger-ui audio:not([controls]){display:none;height:0}.swagger-ui img{border-style:none}.swagger-ui svg:not(:root){overflow:hidden}.swagger-ui button,.swagger-ui input,.swagger-ui optgroup,.swagger-ui select,.swagger-ui textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}.swagger-ui button,.swagger-ui input{overflow:visible}.swagger-ui button,.swagger-ui select{text-transform:none}.swagger-ui [type=reset],.swagger-ui [type=submit],.swagger-ui button,.swagger-ui html [type=button]{-webkit-appearance:button}.swagger-ui [type=button]::-moz-focus-inner,.swagger-ui [type=reset]::-moz-focus-inner,.swagger-ui [type=submit]::-moz-focus-inner,.swagger-ui button::-moz-focus-inner{border-style:none;padding:0}.swagger-ui [type=button]:-moz-focusring,.swagger-ui [type=reset]:-moz-focusring,.swagger-ui [type=submit]:-moz-focusring,.swagger-ui button:-moz-focusring{outline:1px dotted ButtonText}.swagger-ui fieldset{padding:.35em .75em .625em}.swagger-ui legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}.swagger-ui progress{display:inline-block;vertical-align:baseline}.swagger-ui textarea{overflow:auto}.swagger-ui [type=checkbox],.swagger-ui [type=radio]{box-sizing:border-box;padding:0}.swagger-ui [type=number]::-webkit-inner-spin-button,.swagger-ui [type=number]::-webkit-outer-spin-button{height:auto}.swagger-ui [type=search]{-webkit-appearance:textfield;outline-offset:-2px}.swagger-ui [type=search]::-webkit-search-cancel-button,.swagger-ui [type=search]::-webkit-search-decoration{-webkit-appearance:none}.swagger-ui ::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}.swagger-ui details,.swagger-ui menu{display:block}.swagger-ui summary{display:list-item}.swagger-ui canvas{display:inline-block}.swagger-ui [hidden],.swagger-ui template{display:none}.swagger-ui .debug *{outline:1px solid gold}.swagger-ui .debug-white *{outline:1px solid #fff}.swagger-ui .debug-black *{outline:1px solid #000}.swagger-ui .debug-grid{background:transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTRDOTY4N0U2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTRDOTY4N0Q2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3NjY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3NzY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PsBS+GMAAAAjSURBVHjaYvz//z8DLsD4gcGXiYEAGBIKGBne//fFpwAgwAB98AaF2pjlUQAAAABJRU5ErkJggg==) repeat 0 0}.swagger-ui .debug-grid-16{background:transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eLine truncated
|
||||
|
||||
/*# sourceMappingURL=swagger-ui.css.map*/
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["webpack:///./src/style/main.scss","webpack:///./src/style/_type.scss","webpack:///./src/style/_variables.scss","webpack:///./node_modules/tachyons-sass/scss/_normalize.scss","webpack:///./node_modules/tachyons-sass/scss/_debug-children.scss","webpack:///./node_modules/tachyons-sass/scss/_debug-grid.scss","webpack:///./node_modules/tachyons-sass/scss/_box-sizing.scss","webpack:///./node_modules/tachyons-sass/scss/_aspect-ratios.scss","webpack:///./node_modules/tachyons-sass/scss/_images.scss","webpack:///./node_modules/tachyons-sass/scss/_background-size.scss","webpack:///./node_modules/tachyons-sass/scss/_background-position.scss","webpack:///./node_modules/tachyons-sass/scss/_outlines.scss","webpack:///./node_modules/tachyons-sass/scss/_borders.scss","webpack:///./node_modules/tachyons-sass/scss/_border-colors.scss","webpack:///./node_modules/tachyons-sass/scss/_border-radius.scss","webpack:///./node_modules/tachyons-sass/scss/_border-style.scss","webpack:///./node_modules/tachyons-sass/scss/_border-widths.scss","webpack:///./node_modules/tachyons-sass/scss/_box-shadow.scss","webpack:///./node_modules/tachyons-sass/scss/_code.scss","webpack:///./node_modules/tachyons-sass/scss/_coordinates.scss","webpack:///./node_modules/tachyons-sass/scss/_clears.scss","webpack:///./node_modules/tachyons-sass/scss/_flexbox.scss","webpack:///./node_modules/tachyons-sass/scss/_display.scss","webpack:///./node_modules/tachyons-sass/scss/_floats.scss","webpack:///./node_modules/tachyons-sass/scss/_font-family.scss","webpack:///./node_modules/tachyons-sass/scss/_font-style.scss","webpack:///./node_modules/tachyons-sass/scss/_font-weight.scss","webpack:///./node_modules/tachyons-sass/scss/_forms.scss","webpack:///./node_modules/tachyons-sass/scss/_heights.scss","webpack:///./node_modules/tachyons-sass/scss/_letter-spacing.scss","webpack:///./node_modules/tachyons-sass/scss/_line-height.scss","webpack:///./node_modules/tachyons-sass/scss/_links.scss","webpack:///./node_modules/tachyons-sass/scss/_lists.scss","webpack:///./node_modules/tachyons-sass/scss/_max-widths.scss","webpack:///./node_modules/tachyons-sass/scss/_widths.scss","webpack:///./node_modules/tachyons-sass/scss/_overflow.scss","webpack:///./node_modules/tachyons-sass/scss/_position.scss","webpack:///./node_modules/tachyons-sass/scss/_opacity.scss","webpack:///./node_modules/tachyons-sass/scss/_rotations.scss","webpack:///./node_modules/tachyons-sass/scss/_skins.scss","webpack:///./node_modules/tachyons-sass/scss/_skins-pseudo.scss","webpack:///./node_modules/tachyons-sass/scss/_spacing.scss","webpack:///./node_modules/tachyons-sass/scss/_variables.scss","webpack:///./node_modules/tachyons-sass/scss/_negative-margins.scss","webpack:///./node_modules/tachyons-sass/scss/_tables.scss","webpack:///./node_modules/tachyons-sass/scss/_text-decoration.scss","webpack:///./node_modules/tachyons-sass/scss/_text-align.scss","webpack:///./node_modules/tachyons-sass/scss/_text-transform.scss","webpack:///./node_modules/tachyons-sass/scss/_type-scale.scss","webpack:///./node_modules/tachyons-sass/scss/_typography.scss","webpack:///./node_modules/tachyons-sass/scss/_utilities.scss","webpack:///./node_modules/tachyons-sass/scss/_visibility.scss","webpack:///./node_modules/tachyons-sass/scss/_white-space.scss","webpack:///./node_modules/tachyons-sass/scss/_vertical-align.scss","webpack:///./node_modules/tachyons-sass/scss/_hovers.scss","webpack:///./node_modules/tachyons-sass/scss/_z-index.scss","webpack:///./node_modules/tachyons-sass/scss/_nested.scss","webpack:///./src/style/_layout.scss","webpack:///./src/style/_buttons.scss","webpack:///./src/style/_mixins.scss","webpack:///./src/style/_form.scss","webpack:///./src/style/_modal.scss","webpack:///./src/style/_models.scss","webpack:///./src/style/_servers.scss","webpack:///./src/style/_table.scss","webpack:///./src/style/_topbar.scss","webpack:///./src/style/_information.scss","webpack:///./src/style/_authorize.scss","webpack:///./src/style/_errors.scss","webpack:///./src/style/_split-pane-mode.scss","webpack:///./src/style/_markdown.scss"],"names":[],"mappings":"AAAA,YCII,aCYU;ECTd,4EFLI,sBDqhCJ,CGrgCA,iBAEE,0BACA,8BAFA,gBH4gCF,CGhgCA,iBACE,QHkgCF,CG3/BA,gHAME,aH6/BF,CGr/BA,eACE,cACA,cHu/BF,CG5+BA,2DAGE,aH++BF,CGx+BA,mBACE,eH0+BF,CGl+BA,eACE,uBACA,SACA,gBHu+BF,CG/9BA,gBACE,gCACA,aHm+BF,CGx9BA,cAEE,qCADA,4BH69BF,CGp9BA,wBACE,mBACA,0BACA,yEHy9BF,CGl9BA,iCAEE,oBASA,kBH28BF,CGn8BA,kDAGE,gCACA,aH28BF,CGp8BA,gBACE,iBHs8BF,CG/7BA,iBACE,sBACA,UHi8BF,CG17BA,kBACE,aH47BF,CGp7BA,gCAEE,cACA,cACA,kBACA,uBHs7BF,CGn7BA,gBACE,aHq7BF,CGl7BA,gBACE,SHo7BF,CG16BA,oCAEE,oBH46BF,CGr6BA,kCACE,aACA,QHu6BF,CGh6BA,gBACE,iBHk6BF,CG35BA,2BACE,eH65BF,CGl5BA,kGAKE,uBACA,eACA,iBACA,QHw5BF,CGh5BA,qCAEE,gBHm5BF,CG34BA,sCAEE,mBH84BF,CGr4BA,qGAIE,yBHw4BF,CGj4BA,wKAIE,kBACA,SHm4BF,CG53BA,4JAIE,6BH83BF,CGv3BA,qBACE,0BHy3BF,CG/2BA,mBACE,sBACA,cACA,cACA,eACA,UACA,kBHu3BF,CG/2BA,qBACE,qBACA,uBHm3BF,CG52BA,qBACE,aH82BF,CGt2BA,qDAEE,sBACA,SH02BLine truncated
|
||||