Merge pull request 'main' (#2) from main into dev

Reviewed-on: https://codeberg.org/HexagonCDN/Hyperpipe/pulls/2
This commit is contained in:
HexagonCDN 2022-12-19 09:08:53 +00:00
commit 772664ae27
40 changed files with 989 additions and 838 deletions

View file

@ -1,5 +1,9 @@
.git .git
.gitea
.gitignore .gitignore
.dockerignore .dockerignore
.prettierignore
.prettierrc.json
.woodpecker.yml
*.md *.md
*.txt *.txt

39
.woodpecker.yml Normal file
View file

@ -0,0 +1,39 @@
pipeline:
build:
image: node:alpine
commands:
- npm install
- npm run build
when:
path:
exclude: [ '*.md', '.gitea/*' ]
event: ['push', 'pull_request']
docker:
image: woodpeckerci/plugin-docker-buildx
settings:
platforms: linux/amd64,linux/arm64
repo: codeberg.org/hyperpipe/hyperpipe
registry: codeberg.org
tags: latest
username: snematoda
password:
from_secret: cb_token
when:
branch: main
path:
exclude: [ '*.md', '.gitea/*' ]
event: ['push']
deploy:
image: node:alpine
commands:
- npm install surge
- cp dist/index.html dist/200.html
- npx surge ./dist hyperpipe.surge.sh
secrets: [surge_login, surge_token]
when:
branch: main
path:
exclude: [ '*.md', '.gitea/*' ]
event: ['push']

View file

@ -1,19 +0,0 @@
pipeline:
build:
image: node:alpine
commands:
- npm install
- npm run build
when:
event: [push, pull_request]
deploy:
image: node:alpine
commands:
- npm install surge
- cp dist/index.html dist/200.html
- npx surge ./dist hyperpipe.surge.sh
secrets: [surge_login, surge_token]
when:
branch: main
event: push

View file

@ -1,20 +1,15 @@
FROM node:alpine AS build FROM --platform=$BUILDPLATFORM node:lts AS build
ARG api
ARG pipedapi
WORKDIR /app/ WORKDIR /app/
COPY . . COPY . .
RUN sed -i "s/hyperpipeapi.onrender.com/$api/g" index.html src/scripts/fetch.js RUN --mount=type=cache,target=/root/.cache/node \
--mount=type=cache,target=/app/node_modules \
RUN sed -i "s/pipedapi.kavin.rocks/$pipedapi/g" index.html src/scripts/fetch.js npm install --prefer-offline && \
RUN npm install && \
npm run build npm run build
FROM nginx:alpine FROM --platform=$BUILDPLATFORM nginx:stable
COPY --from=build /app/dist/ /usr/share/nginx/html/ COPY --from=build /app/dist/ /usr/share/nginx/html/
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf COPY docker/nginx.conf /etc/nginx/conf.d/default.conf

View file

@ -1,14 +0,0 @@
version: '3'
services:
hyperpipe-frontend:
image: hyperpipe-frontend
build:
context: .
args:
pipedapi: pipedapi.kavin.rocks
api: hyperpipeapi.onrender.com
container_name: hyperpipe-frontend
restart: unless-stopped
ports:
- '8080:80'

View file

@ -9,7 +9,6 @@
<link rel="icon" href="/favicon.svg" /> <link rel="icon" href="/favicon.svg" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" /> <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<meta name="msapplication-TileColor" content="#181818" /> <meta name="msapplication-TileColor" content="#181818" />
<link rel="preconnect" href="https://cdn.jsdelivr.net" />
<link rel="preconnect" href="https://hyperpipeapi.onrender.com" /> <link rel="preconnect" href="https://hyperpipeapi.onrender.com" />
<link rel="dns-prefetch" href="https://pipedapi.kavin.rocks" /> <link rel="dns-prefetch" href="https://pipedapi.kavin.rocks" />
<link rel="dns-prefetch" href="https://hyperpipe-proxy.onrender.com" /> <link rel="dns-prefetch" href="https://hyperpipe-proxy.onrender.com" />
@ -29,7 +28,10 @@
<title>Hyperpipe</title> <title>Hyperpipe</title>
</head> </head>
<body> <body>
<noscript>Kind Sir, Please Give Me JavaScript</noscript> <noscript
>JavaScript is required for this site to function. Please enable it in
your browser or browser extension settings.</noscript
>
<div id="app"></div> <div id="app"></div>

1174
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -15,15 +15,15 @@
"dompurify": "^2.4.1", "dompurify": "^2.4.1",
"mux.js": "^6.2.0", "mux.js": "^6.2.0",
"peerjs": "^1.4.7", "peerjs": "^1.4.7",
"pinia": "^2.0.26", "pinia": "^2.0.28",
"shaka-player": "^4.3.0", "shaka-player": "^4.3.1",
"stream-browserify": "^3.0.0", "stream-browserify": "^3.0.0",
"vue": "^3.2.38", "vue": "^3.2.38",
"xml-js": "^1.6.11" "xml-js": "^1.6.11"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^3.2.0", "@vitejs/plugin-vue": "^4.0.0",
"prettier": "^2.8.0", "prettier": "^2.8.1",
"vite": "^3.2.4" "vite": "^4.0.0"
} }
} }

View file

@ -150,7 +150,7 @@ onMounted(() => {
v-if="data.state.art" v-if="data.state.art"
class="art" class="art"
loading="lazy" loading="lazy"
:src="data.state.art" /> :src="data.state.art.replaceAll('&amp;', '&')" />
<div class="wrapper"> <div class="wrapper">
<NowPlaying @get-artist="artist.getArtist" /> <NowPlaying @get-artist="artist.getArtist" />

View file

@ -1,3 +1,3 @@
{ {
"date": "2022-11-30" "date": "2022-12-13"
} }

View file

@ -3,7 +3,7 @@ import { useRand } from '@/scripts/colors.js';
const rand = useRand(); const rand = useRand();
const props = defineProps({ defineProps({
name: String, name: String,
author: { author: {
type: String, type: String,

View file

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, onUpdated } from 'vue'; import { ref } from 'vue';
import Btn from './Btn.vue'; import Btn from './Btn.vue';
import { useStore } from '@/scripts/util.js'; import { useStore } from '@/scripts/util.js';

View file

@ -51,7 +51,9 @@ onMounted(() => {
<template> <template>
<template v-if="data.options.length > 0"> <template v-if="data.options.length > 0">
<select :value="id" class="input" @input="id = $event.target.value"> <select :value="id" class="input" @input="id = $event.target.value">
<option v-for="i in data.options" :value="i.id">{{ i.title }}</option> <option v-for="i in data.options" :value="i.id" :key="i.id">
{{ i.title }}
</option>
</select> </select>
</template> </template>
@ -60,6 +62,7 @@ onMounted(() => {
<div class="grid-3 circle"> <div class="grid-3 circle">
<AlbumItem <AlbumItem
v-for="i in data.artists" v-for="i in data.artists"
:key="i.id"
:name="i.title" :name="i.title"
:author="i.subtitle" :author="i.subtitle"
:art="i.thumbnails[1].url" :art="i.thumbnails[1].url"
@ -72,6 +75,7 @@ onMounted(() => {
<div class="grid"> <div class="grid">
<SongItem <SongItem
v-for="i in data.songs" v-for="i in data.songs"
:key="i.id"
:title="i.title" :title="i.title"
:author="i.subtitle" :author="i.subtitle"
:channel="'/channel/' + i.subId" :channel="'/channel/' + i.subId"

View file

@ -1,5 +1,5 @@
<script setup> <script setup>
import { reactive, ref, onMounted, onUnmounted } from 'vue'; import { reactive, onMounted } from 'vue';
import { useResults } from '@/stores/results.js'; import { useResults } from '@/stores/results.js';
@ -62,19 +62,21 @@ onMounted(get);
<h2 class="head">{{ data.title }}</h2> <h2 class="head">{{ data.title }}</h2>
<template v-for="type in ['featured', 'spotlight', 'community']"> <template
v-for="type in ['featured', 'spotlight', 'community']"
:key="type">
<h3 class="head">{{ t('title.' + type) }}</h3> <h3 class="head">{{ t('title.' + type) }}</h3>
<div class="grid-3"> <div class="grid-3">
<template v-for="i in data[type]"> <AlbumItem
<AlbumItem v-for="i in data[type]"
:name="i.title" :key="i.id"
:author="i.subtitle" :name="i.title"
:art="i.thumbnails[0].url" :author="i.subtitle"
@open-album=" :art="i.thumbnails[0].url"
getAlbum('/playlist?list=' + i.id); @open-album="
nav.state.page = 'home'; getAlbum('/playlist?list=' + i.id);
" /> nav.state.page = 'home';
</template> " />
</div> </div>
</template> </template>
</template> </template>
@ -86,6 +88,7 @@ onMounted(get);
<button <button
v-for="i in btns.moods" v-for="i in btns.moods"
class="btn" class="btn"
:key="i.id"
:style="`--btn-color: ${i.subtitle};`" :style="`--btn-color: ${i.subtitle};`"
@click="get(i.id)"> @click="get(i.id)">
{{ i.title }} {{ i.title }}
@ -98,6 +101,7 @@ onMounted(get);
<button <button
v-for="i in btns.genres" v-for="i in btns.genres"
class="btn" class="btn"
:key="i.id"
:style="`--btn-color: ${i.subtitle};`" :style="`--btn-color: ${i.subtitle};`"
@click="get(i.id)"> @click="get(i.id)">
{{ i.title }} {{ i.title }}

View file

@ -1,6 +1,4 @@
<script setup> <script setup>
import { reactive } from 'vue';
import SearchBar from '@/components/SearchBar.vue'; import SearchBar from '@/components/SearchBar.vue';
import IcoHyp from '@/assets/icons/IcoHyp.vue'; import IcoHyp from '@/assets/icons/IcoHyp.vue';

View file

@ -30,8 +30,9 @@ const { t } = useI18n(),
results = useResults(), results = useResults(),
nav = useNav(); nav = useNav();
const emit = defineEmits(['play-urls', 'open-playlist']), defineEmits(['play-urls', 'open-playlist']);
list = ref([]),
const list = ref([]),
show = reactive({ show = reactive({
new: false, new: false,
sync: false, sync: false,
@ -137,7 +138,7 @@ const Login = async () => {
}, },
createPlaylist = async () => { createPlaylist = async () => {
if (text.value) { if (text.value) {
const res = await useAuthCreatePlaylist(text.value); await useAuthCreatePlaylist(text.value);
getPlaylists(); getPlaylists();
show.new = false; show.new = false;
@ -312,24 +313,24 @@ onMounted(async () => {
<h2 v-if="list.length > 0">{{ t('playlist.local') }}</h2> <h2 v-if="list.length > 0">{{ t('playlist.local') }}</h2>
<div class="grid-3"> <div class="grid-3">
<template v-for="i in list"> <AlbumItem
<AlbumItem v-for="i in list"
:name="i.name" :key="i.name"
:author="t('title.songs') + ' • ' + i.urls.length" :name="i.name"
:grad="useRand()" :author="t('title.songs') + ' • ' + i.urls.length"
@open-album="Open(i.name)" /> :grad="useRand()"
</template> @open-album="Open(i.name)" />
</div> </div>
<h2 class="login-h">{{ t('playlist.remote') }}</h2> <h2 class="login-h">{{ t('playlist.remote') }}</h2>
<div v-if="auth" class="grid-3"> <div v-if="auth" class="grid-3">
<template v-for="i in user.playlists"> <AlbumItem
<AlbumItem v-for="i in user.playlists"
:name="i.name.replace('Playlist - ', '')" :key="i.id"
:art="pathname(i.thumbnail) != '/' ? i.thumbnail : undefined" :name="i.name.replace('Playlist - ', '')"
@open-album="$emit('open-playlist', '/playlists?list=' + i.id)" /> :art="pathname(i.thumbnail) != '/' ? i.thumbnail : undefined"
</template> @open-album="$emit('open-playlist', '/playlists?list=' + i.id)" />
</div> </div>
<form v-else class="login" @submit.prevent> <form v-else class="login" @submit.prevent>
<input <input

View file

@ -1,12 +1,5 @@
<script setup> <script setup>
import { import { ref, watch, onMounted, onBeforeUnmount, onUnmounted } from 'vue';
ref,
watch,
onMounted,
onUpdated,
onBeforeUnmount,
onUnmounted,
} from 'vue';
import muxjs from 'mux.js'; import muxjs from 'mux.js';
window.muxjs = muxjs; window.muxjs = muxjs;
@ -64,7 +57,7 @@ async function Stream() {
if (shaka.Player.isBrowserSupported) { if (shaka.Player.isBrowserSupported) {
const audioPlayer = new shaka.Player(audio.value); const audioPlayer = new shaka.Player(audio.value);
const codecs = useStore().getItem('codec'); const codecs = store.getItem('codec');
audioPlayer audioPlayer
.getNetworkingEngine() .getNetworkingEngine()
@ -94,7 +87,7 @@ async function Stream() {
}); });
} }
const quality = useStore().getItem('quality'); const quality = store.getItem('quality');
if (url) { if (url) {
window.audioPlayer window.audioPlayer

View file

@ -12,26 +12,28 @@ defineEmits(['playthis']);
<template> <template>
<Transition name="fade"> <Transition name="fade">
<div class="pl-modal placeholder" :data-placeholder="t('playlist.add')"> <div class="pl-modal placeholder" :data-placeholder="t('playlist.add')">
<template v-for="plurl in data.state.urls"> <div
<div class="pl-item" @click="$emit('playthis', plurl)"> v-for="plurl in data.state.urls"
<span class="pl-item"
v-if="data.state.url == plurl.url" :key="plurl.url"
class="bars-wrap" @click="$emit('playthis', plurl)">
:class="player.state.status"> <span
<div class="bars"></div> v-if="data.state.url == plurl.url"
<div class="bars"></div> class="bars-wrap"
<div class="bars"></div> :class="player.state.status">
</span> <div class="bars"></div>
<div v-else-if="plurl.thumbnails" class="pl-img"> <div class="bars"></div>
<img <div class="bars"></div>
:src="plurl.thumbnails[0].url" </span>
:height="plurl.thumbnails[0].height" <div v-else-if="plurl.thumbnails" class="pl-img">
:width="plurl.thumbnails[0].width" <img
loading="lazy" /> :src="plurl.thumbnails[0].url"
</div> :height="plurl.thumbnails[0].height"
<span class="pl-main caps">{{ plurl.title }}</span> :width="plurl.thumbnails[0].width"
loading="lazy" />
</div> </div>
</template> <span class="pl-main caps">{{ plurl.title }}</span>
</div>
</div> </div>
</Transition> </Transition>
</template> </template>

View file

@ -107,7 +107,9 @@ onMounted(() => {
class="input" class="input"
:value="getStore('locale') || 'en'" :value="getStore('locale') || 'en'"
@change="setLang($event.target.value)"> @change="setLang($event.target.value)">
<option v-for="i in SUPPORTED_LOCALES" :value="i.code">{{ i.name }}</option> <option v-for="i in SUPPORTED_LOCALES" :value="i.code" :key="i.code">
{{ i.name }}
</option>
</select> </select>
<h2>{{ t('pref.tab') }}</h2> <h2>{{ t('pref.tab') }}</h2>
@ -205,7 +207,7 @@ onMounted(() => {
<th>{{ t('instances.loc') }}</th> <th>{{ t('instances.loc') }}</th>
</tr> </tr>
</thead> </thead>
<tbody v-for="i in hypInstances"> <tbody v-for="i in hypInstances" :key="i.name">
<tr> <tr>
<td> <td>
{{ i.name }} {{ i.name }}
@ -273,7 +275,7 @@ onMounted(() => {
<th>{{ t('instances.version') }}</th> <th>{{ t('instances.version') }}</th>
</tr> </tr>
</thead> </thead>
<tbody v-for="i in instances"> <tbody v-for="i in instances" :key="i.name">
<tr> <tr>
<td> <td>
{{ i.name.replace('Official', 'Default') }} {{ i.name.replace('Official', 'Default') }}

View file

@ -1,12 +1,5 @@
<script setup> <script setup>
import { import { ref, watch, onActivated, onUpdated, onDeactivated } from 'vue';
ref,
reactive,
watch,
onActivated,
onUpdated,
onDeactivated,
} from 'vue';
import Btn from './Btn.vue'; import Btn from './Btn.vue';
import SongItem from './SongItem.vue'; import SongItem from './SongItem.vue';
@ -241,11 +234,12 @@ onDeactivated(() => {
<button <button
v-for="f in filters" v-for="f in filters"
class="filter caps" class="filter caps"
:key="f"
:data-active="f == filter"
@click=" @click="
filter = f; filter = f;
getSearch(nav.state.search); getSearch(nav.state.search);
" ">
:data-active="f == filter">
{{ t('title.' + f.split('_')[1]) }} {{ t('title.' + f.split('_')[1]) }}
</button> </button>
</div> </div>
@ -255,35 +249,35 @@ onDeactivated(() => {
class="search-wrap"> class="search-wrap">
<h2 v-if="!isSearch">{{ t('title.songs') }}</h2> <h2 v-if="!isSearch">{{ t('title.songs') }}</h2>
<div class="grid"> <div class="grid">
<template v-for="(song, index) in results.items.songs.items"> <SongItem
<SongItem v-for="(song, index) in results.items.songs.items"
:index="index" :key="song.url || song.id"
:playlistId="song.playlistId" :index="index"
:author="song.uploaderName || song.subtitle" :playlistId="song.playlistId"
:title="song.title || song.name" :author="song.uploaderName || song.subtitle"
:channel="song.uploaderUrl || '/channel/' + song.subId" :title="song.title || song.name"
:play="song.url || '/watch?v=' + song.id" :channel="song.uploaderUrl || '/channel/' + song.subId"
:art=" :play="song.url || '/watch?v=' + song.id"
song.thumbnail || song.thumbnails[1]?.url || song.thumbnails[0]?.url :art="
" song.thumbnail || song.thumbnails[1]?.url || song.thumbnails[0]?.url
@remove="removeSong" "
@open-song=" @remove="removeSong"
$emit('play-urls', [ @open-song="
{ $emit('play-urls', [
url: song.url || '/watch?v=' + song.id, {
title: song.title || song.name, url: song.url || '/watch?v=' + song.id,
thumbnails: [ title: song.title || song.name,
{ thumbnails: [
url: {
song.thumbnail || url:
song.thumbnails[1]?.url || song.thumbnail ||
song.thumbnails[0]?.url, song.thumbnails[1]?.url ||
}, song.thumbnails[0]?.url,
], },
}, ],
]) },
" /> ])
</template> " />
</div> </div>
<a <a
v-if="artist.state.playlistId" v-if="artist.state.playlistId"
@ -301,15 +295,15 @@ onDeactivated(() => {
class="search-wrap"> class="search-wrap">
<h2 v-if="!isSearch">{{ t('title.albums') }}</h2> <h2 v-if="!isSearch">{{ t('title.albums') }}</h2>
<div class="grid-3"> <div class="grid-3">
<template v-for="album in results.items.albums.items"> <AlbumItem
<AlbumItem v-for="album in results.items.albums.items"
:author="album.uploaderName || album.subtitle" :key="album.url || album.id"
:name="album.name || album.title" :author="album.uploaderName || album.subtitle"
:art="album.thumbnail || album.thumbnails[0].url" :name="album.name || album.title"
@open-album=" :art="album.thumbnail || album.thumbnails[0].url"
results.getAlbum(album.url || '/playlist?list=' + album.id) @open-album="
" /> results.getAlbum(album.url || '/playlist?list=' + album.id)
</template> " />
</div> </div>
</div> </div>
@ -320,6 +314,7 @@ onDeactivated(() => {
<div class="grid-3"> <div class="grid-3">
<AlbumItem <AlbumItem
v-for="pl in results.items.playlists.items" v-for="pl in results.items.playlists.items"
:key="pl.url"
:author="pl.videos + ' Songs • ' + pl.uploaderName" :author="pl.videos + ' Songs • ' + pl.uploaderName"
:name="pl.name" :name="pl.name"
:art="pl.thumbnail" :art="pl.thumbnail"
@ -332,13 +327,13 @@ onDeactivated(() => {
class="search-wrap"> class="search-wrap">
<h2>{{ t('title.singles') }}</h2> <h2>{{ t('title.singles') }}</h2>
<div class="grid-3"> <div class="grid-3">
<template v-for="single in results.items.singles.items"> <AlbumItem
<AlbumItem v-for="single in results.items.singles.items"
:author="single.subtitle" :key="single.id"
:name="single.title" :author="single.subtitle"
:art="single.thumbnails[0].url" :name="single.title"
@open-album="results.getAlbum('/playlist?list=' + single.id)" /> :art="single.thumbnails[0].url"
</template> @open-album="results.getAlbum('/playlist?list=' + single.id)" />
</div> </div>
</div> </div>
@ -355,18 +350,17 @@ onDeactivated(() => {
}} }}
</h2> </h2>
<div class="grid-3 circle"> <div class="grid-3 circle">
<template <AlbumItem
v-for="a in results.items.artists v-for="a in results.items.artists
? results.items.artists.items ? results.items.artists.items
: results.items.recommendedArtists.items"> : results.items.recommendedArtists.items"
<AlbumItem :key="a.id || a.url"
:author="a.subtitle" :author="a.subtitle"
:name="a.name || a.title" :name="a.name || a.title"
:art="a.thumbnail || a.thumbnails[0].url" :art="a.thumbnail || a.thumbnails[0].url"
@open-album=" @open-album="
artist.getArtist(a.id || a.url.replace('/channel/', '')) artist.getArtist(a.id || a.url.replace('/channel/', ''))
" /> " />
</template>
</div> </div>
</div> </div>
</template> </template>

View file

@ -5,6 +5,12 @@ import { useNav, useI18n } from '@/stores/misc.js';
const { t } = useI18n(), const { t } = useI18n(),
show = ref(false), show = ref(false),
nav = useNav(); nav = useNav();
function search(e) {
nav.state.search = e.target.value;
nav.state.page = 'home';
e.target.blur();
}
</script> </script>
<template> <template>
@ -19,11 +25,8 @@ const { t } = useI18n(),
type="search" type="search"
aria-label="Search Input" aria-label="Search Input"
:placeholder="t('title.search') + '...'" :placeholder="t('title.search') + '...'"
@change=" @change="search"
nav.state.search = $event.target.value; @keyup.enter="search"
nav.state.page = 'home';
$event.target.blur();
"
:value="nav.state.search" /> :value="nav.state.search" />
</div> </div>
</Transition> </Transition>

View file

@ -5,7 +5,7 @@ import { getJsonAuth } from '@/scripts/fetch.js';
import { useRand } from '@/scripts/colors.js'; import { useRand } from '@/scripts/colors.js';
import { useStore } from '@/scripts/util.js'; import { useStore } from '@/scripts/util.js';
import { useArtist, useResults } from '@/stores/results.js'; import { useArtist } from '@/stores/results.js';
import { useData, usePlayer } from '@/stores/player.js'; import { useData, usePlayer } from '@/stores/player.js';
const rand = useRand(), const rand = useRand(),

View file

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, reactive, watch } from 'vue'; import { ref, reactive } from 'vue';
import Modal from './Modal.vue'; import Modal from './Modal.vue';
@ -19,8 +19,9 @@ const { t } = useI18n(),
player = usePlayer(), player = usePlayer(),
store = useStore(); store = useStore();
const emit = defineEmits(['save']), defineEmits(['save']);
showme = reactive({
const showme = reactive({
menu: false, menu: false,
pl: false, pl: false,
vol: false, vol: false,
@ -112,29 +113,29 @@ async function Like() {
} }
"> ">
<template #content> <template #content>
<template v-for="i in list"> <div
<div v-for="i in list"
class="flex item" :key="i.name"
@click=" class="flex item"
pl = i.name; @click="
plRemote = false; pl = i.name;
" plRemote = false;
:data-active="pl == i.name && plRemote == false"> "
<span>{{ i.name }}</span :data-active="pl == i.name && plRemote == false">
><span class="ml-auto">{{ i.urls.length || '' }}</span> <span>{{ i.name }}</span
</div> ><span class="ml-auto">{{ i.urls.length || '' }}</span>
</template> </div>
<template v-for="i in remote"> <div
<div v-for="i in remote"
class="flex item" :key="i.id"
@click=" class="flex item"
pl = i.id; @click="
plRemote = true; pl = i.id;
" plRemote = true;
:data-active="pl == i.id && plRemote == true"> "
<span>{{ i.name }}</span> :data-active="pl == i.id && plRemote == true">
</div> <span>{{ i.name }}</span>
</template> </div>
</template> </template>
<template #buttons> <template #buttons>
<button aria-label="Cancel" @click="showme.pl = false"> <button aria-label="Cancel" @click="showme.pl = false">

View file

@ -58,7 +58,7 @@
"dark": "Mračno (Zadano)", "dark": "Mračno (Zadano)",
"dracula": "Drakula", "dracula": "Drakula",
"nord": "Nord", "nord": "Nord",
"blur": "Zamućenje", "blur": "Zamagljeno",
"blur_light": "Zamućenje (Svjetlo)", "blur_light": "Zamućenje (Svjetlo)",
"light": "Svjetlo" "light": "Svjetlo"
}, },

View file

@ -47,7 +47,7 @@
}, },
"lyrics": { "lyrics": {
"load": "Načítání textů", "load": "Načítání textů",
"void": "Žádné texty" "void": "Texty nenalezeny"
}, },
"action": { "action": {
"back": "Zpět", "back": "Zpět",

View file

@ -46,9 +46,9 @@
"best": "am Besten", "best": "am Besten",
"tab": "Standard-Registerkarte", "tab": "Standard-Registerkarte",
"dark": "Dunkel (Standard)", "dark": "Dunkel (Standard)",
"light": "Licht", "light": "Hell",
"blur": "Unschärfe", "blur": "Unschärfe",
"blur_light": "Unschärfe (Licht)", "blur_light": "Unschärfe (hell)",
"dracula": "Dracula", "dracula": "Dracula",
"nord": "Nord" "nord": "Nord"
}, },

View file

@ -14,7 +14,8 @@
"local": "Regional", "local": "Regional",
"remote": "Remoto", "remote": "Remoto",
"search": "Búsqueda", "search": "Búsqueda",
"playlists": "Listas de reproducción" "playlists": "Listas de reproducción",
"feeds": "Fuentes"
}, },
"action": { "action": {
"back": "Atrás", "back": "Atrás",
@ -43,7 +44,13 @@
"auto_queue": "Canciones en la cola de reproducción automáticamente", "auto_queue": "Canciones en la cola de reproducción automáticamente",
"worst": "La peor", "worst": "La peor",
"best": "La mejor", "best": "La mejor",
"tab": "Ficha predeterminada" "tab": "Ficha predeterminada",
"dark": "Oscuro (Por defecto)",
"blur": "Desenfocar",
"light": "Claro",
"blur_light": "Desenfoque (Luz)",
"dracula": "Drácula",
"nord": "Nord"
}, },
"info": { "info": {
"see_all": "Ver todo", "see_all": "Ver todo",

View file

@ -14,7 +14,8 @@
"remote": "À distance", "remote": "À distance",
"community": "Communauté", "community": "Communauté",
"search": "Recherche", "search": "Recherche",
"playlists": "Listes de lecture" "playlists": "Listes de lecture",
"feeds": "Flux"
}, },
"playlist": { "playlist": {
"local": "Listes de lecture locales", "local": "Listes de lecture locales",
@ -35,7 +36,13 @@
"player": "Lecteur audio", "player": "Lecteur audio",
"auto_queue": "Mettre automatiquement les chansons en file d'attente", "auto_queue": "Mettre automatiquement les chansons en file d'attente",
"codec": "Codec", "codec": "Codec",
"tab": "Onglet par défaut" "tab": "Onglet par défaut",
"dark": "Sombre (par défaut)",
"light": "Clair",
"blur": "Flou",
"blur_light": "Flou (clair)",
"dracula": "Dracula",
"nord": "Nord"
}, },
"info": { "info": {
"see_all": "Voir tout", "see_all": "Voir tout",

View file

@ -1,12 +1,15 @@
{ {
"title": { "title": {
"songs": "Cancións", "songs": "Cancións",
"albums": "Álbums", "albums": "Álbums",
"singles": "Singles", "singles": "Singles",
"artists": "Artistas", "artists": "Artistas",
"similar_artists": "Artistas similares", "similar_artists": "Artistas similares",
"moods": "Estado de ánimo", "moods": "Estado de ánimo",
"genres": "Xéneros", "genres": "Xéneros",
"featured": "Destacado" "featured": "Destacado"
} },
"pref": {
"blur": "Desenfoque"
}
} }

View file

@ -9,7 +9,13 @@
"quality": "Qualità", "quality": "Qualità",
"auto": "automatica", "auto": "automatica",
"worst": "la peggia", "worst": "la peggia",
"tab": "Scheda predefinita" "tab": "Scheda predefinita",
"dark": "Scuro (predefinito)",
"light": "Chiaro",
"blur": "Sfocatura",
"blur_light": "Sfocatura (chiaro)",
"dracula": "Dracula",
"nord": "Nord"
}, },
"title": { "title": {
"albums": "Album", "albums": "Album",
@ -25,7 +31,8 @@
"local": "Locale", "local": "Locale",
"search": "Ricerca", "search": "Ricerca",
"featured": "In primo piano", "featured": "In primo piano",
"playlists": "Playlist" "playlists": "Playlist",
"feeds": "Flussi"
}, },
"action": { "action": {
"add": "Aggiungi", "add": "Aggiungi",

74
src/locales/pt_br.json Normal file
View file

@ -0,0 +1,74 @@
{
"title": {
"songs": "Músicas",
"albums": "Álbuns",
"singles": "Singles",
"artists": "Artistas",
"similar_artists": "Artistas Similares",
"playlists": "Playlists",
"moods": "Humores",
"genres": "Gêneros",
"featured": "Destaques",
"spotlight": "Foco",
"community": "Comunidade",
"login": "Entrar",
"local": "Local",
"remote": "Remoto",
"search": "Pesquisar",
"feeds": "Feeds"
},
"action": {
"back": "Voltar",
"add": "Adicionar",
"create": "Criar",
"cancel": "Cancelar",
"send": "Enviar",
"receive": "Receber"
},
"playlist": {
"local": "Playlists Locais",
"remote": "Playlists Remotas",
"name": "Nome da Playlist",
"add": "Adicionar Músicas à Playlist",
"create": "Criar uma nova Playlist",
"sync": "Sincronizar playlists",
"select": "Selecione a Playlist para Adicionar"
},
"pref": {
"theme": "Tema",
"dark": "Escuro (padrão)",
"light": "Claro",
"blur": "Desfocado",
"blur_light": "Desfocado (Claro)",
"dracula": "Dracula",
"nord": "Nord",
"tab": "Aba Padrão",
"player": "Reprodutor de Áudio",
"auto_queue": "Enfileirar músicas automaticamente",
"codec": "Codec",
"quality": "Qualidade",
"auto": "automática",
"best": "melhor",
"worst": "pior",
"volume": "Volume Padrão"
},
"info": {
"see_all": "Ver Tudo",
"search": "Comece a pesquisar",
"no_info": "Nenhuma Informação Disponível"
},
"instances": {
"hyp": "Instância do Hyperpipe",
"piped": "Instância do Piped",
"auth": "Instância de Autenticação",
"name": "Nome",
"loc": "Localizações",
"cdn": "CDN",
"up_to_date": "Atualizado",
"version": "Versão"
},
"lyrics": {
"load": "Buscando Letras",
"void": "Sem Letras"
}
}

View file

@ -14,7 +14,8 @@
"login": "Autentificare", "login": "Autentificare",
"search": "Căutare", "search": "Căutare",
"local": "Local", "local": "Local",
"songs": "Melodii" "songs": "Melodii",
"feeds": "Fluxuri"
}, },
"action": { "action": {
"back": "Înapoi", "back": "Înapoi",
@ -43,7 +44,13 @@
"best": "cel mai bun", "best": "cel mai bun",
"auto": "auto", "auto": "auto",
"worst": "cel mai rău", "worst": "cel mai rău",
"volume": "Volum implicit" "volume": "Volum implicit",
"blur": "Blur",
"dark": "Întunecat (implicit)",
"light": "Luminat",
"blur_light": "Blur (Lumină)",
"dracula": "Dracula",
"nord": "Nord"
}, },
"instances": { "instances": {
"cdn": "CDN", "cdn": "CDN",

View file

@ -14,7 +14,8 @@
"remote": "Удалённый", "remote": "Удалённый",
"albums": "Альбомы", "albums": "Альбомы",
"spotlight": "Поиск", "spotlight": "Поиск",
"playlists": "Плейлисты" "playlists": "Плейлисты",
"feeds": "Фиды"
}, },
"action": { "action": {
"back": "Назад", "back": "Назад",
@ -43,7 +44,13 @@
"volume": "Громкость по умолчанию", "volume": "Громкость по умолчанию",
"quality": "Качество", "quality": "Качество",
"best": "лучшее", "best": "лучшее",
"tab": "Вкладка по умолчанию" "tab": "Вкладка по умолчанию",
"dark": "Темный (по умолчанию)",
"light": "Светлый",
"blur": "Размытый",
"blur_light": "Размытый (светлый)",
"dracula": "Дракула",
"nord": "Норд"
}, },
"instances": { "instances": {
"piped": "Инстанс Piped", "piped": "Инстанс Piped",

View file

@ -46,7 +46,7 @@
"volume": "Подразумевана јачина звука", "volume": "Подразумевана јачина звука",
"tab": "Подразумевана Картица", "tab": "Подразумевана Картица",
"light": "Светло", "light": "Светло",
"blur": "Замућење", "blur": "Замагљено",
"dracula": "Дракула", "dracula": "Дракула",
"nord": "Норд", "nord": "Норд",
"dark": "Мрачно (Подразумевано)", "dark": "Мрачно (Подразумевано)",

View file

@ -14,7 +14,8 @@
"genres": "Türler", "genres": "Türler",
"moods": "Ruh hâli", "moods": "Ruh hâli",
"spotlight": "Spot ışıkları", "spotlight": "Spot ışıkları",
"remote": "Uzak" "remote": "Uzak",
"feeds": "Akışlar"
}, },
"action": { "action": {
"back": "Geri", "back": "Geri",
@ -38,12 +39,18 @@
"player": "Ses Çalar", "player": "Ses Çalar",
"auto_queue": "Şarkıları Otomatik Olarak Sırala", "auto_queue": "Şarkıları Otomatik Olarak Sırala",
"codec": "Codec", "codec": "Codec",
"tab": "Varsayılan Sekme", "tab": "Öntanımlı Sekme",
"quality": "Kalite", "quality": "Kalite",
"auto": "otomatik", "auto": "otomatik",
"best": "en iyi", "best": "en iyi",
"worst": "en kötü", "worst": "en kötü",
"volume": "Varsayılan Ses Düzeyi" "volume": "Öntanımlı Ses Düzeyi",
"dark": "Koyu (Öntanımlı)",
"light": "Açık",
"blur": "Bulanıklaştır",
"blur_light": "Bulanıklaştır (Açık)",
"dracula": "Dracula",
"nord": "Nord"
}, },
"info": { "info": {
"see_all": "Hepsini Gör", "see_all": "Hepsini Gör",

View file

@ -10,7 +10,9 @@
"singles": "Các đĩa đơn", "singles": "Các đĩa đơn",
"login": "Đăng nhập", "login": "Đăng nhập",
"moods": "Chủ đề", "moods": "Chủ đề",
"playlists": "Các danh sách phát" "playlists": "Các danh sách phát",
"featured": "Nổi bật",
"feeds": "Nguồn cấp dữ liệu"
}, },
"pref": { "pref": {
"auto": "tự động", "auto": "tự động",
@ -26,8 +28,9 @@
"blur": "Mờ", "blur": "Mờ",
"blur_light": "Mờ (Sáng)", "blur_light": "Mờ (Sáng)",
"nord": "Nord", "nord": "Nord",
"dracula": "Tím", "dracula": "Dracula",
"auto_queue": "Tự động xếp hàng các bài hát" "auto_queue": "Tự động xếp hàng các bài hát",
"tab": "Thẻ mặc định"
}, },
"action": { "action": {
"cancel": "Hủy", "cancel": "Hủy",
@ -42,15 +45,22 @@
"name": "Tên", "name": "Tên",
"cdn": "CDN", "cdn": "CDN",
"loc": "Các vị trí", "loc": "Các vị trí",
"up_to_date": "Phiên bản mới nhất" "up_to_date": "Phiên bản mới nhất",
"auth": "Instance xác thực"
}, },
"playlist": { "playlist": {
"create": "Tạo một danh sách phát mới", "create": "Tạo một danh sách phát mới",
"name": "Tên danh sách phát", "name": "Tên danh sách phát",
"add": "Thêm bài hát vào danh sách phát", "add": "Thêm bài hát vào danh sách phát",
"sync": "Đồng bộ hóa các danh sách phát" "sync": "Đồng bộ hóa các danh sách phát",
"select": "Lựa chọn danh sách phát để thêm"
}, },
"info": { "info": {
"search": "Bắt đầu tìm kiếm" "search": "Bắt đầu tìm kiếm",
"see_all": "Xem tất cả"
},
"lyrics": {
"load": "Tìm lời bài hát",
"void": "Không có lời bài hát"
} }
} }

View file

@ -23,7 +23,8 @@
"songs": "歌曲", "songs": "歌曲",
"albums": "专辑", "albums": "专辑",
"login": "登录", "login": "登录",
"playlists": "播放列表" "playlists": "播放列表",
"feeds": "订阅源"
}, },
"action": { "action": {
"add": "添加", "add": "添加",
@ -43,7 +44,13 @@
"worst": "最差", "worst": "最差",
"volume": "默认音量", "volume": "默认音量",
"theme": "主题", "theme": "主题",
"tab": "默认标签页" "tab": "默认标签页",
"blur": "模糊",
"dark": "深色(默认)",
"light": "亮色",
"blur_light": "模糊(亮色)",
"dracula": "Dracula",
"nord": "Nord"
}, },
"info": { "info": {
"see_all": "显示全部", "see_all": "显示全部",

View file

@ -92,6 +92,10 @@ export const SUPPORTED_LOCALES = [
code: 'zh_Hans', code: 'zh_Hans',
name: '中文 (简体)', name: '中文 (简体)',
}, },
{
code: 'pt_br',
name: 'Português Brasileiro',
},
]; ];
export const useNav = defineStore('nav', () => { export const useNav = defineStore('nav', () => {

View file

@ -29,7 +29,7 @@ export const useData = defineStore('data', () => {
console.log(json); console.log(json);
state.art = json.thumbnailUrl; state.art = json.thumbnailUrl.replaceAll('&amp;', '&');
state.description = json.description; state.description = json.description;
state.title = json.title.replaceAll('&amp;', '&'); state.title = json.title.replaceAll('&amp;', '&');
state.artist = json.uploader state.artist = json.uploader

View file

@ -68,7 +68,7 @@ export const useResults = defineStore('results', () => {
useNav().state.page = 'home'; useNav().state.page = 'home';
next.value = next.value =
json.nextpage || json.nextpage != 'null' json.nextpage && json.nextpage != 'null'
? hash + '?nextpage=' + encodeURIComponent(json.nextpage) ? hash + '?nextpage=' + encodeURIComponent(json.nextpage)
: null; : null;
} }