mirror of
https://codeberg.org/Hyperpipe/Hyperpipe
synced 2025-06-27 20:58:01 +02:00
Merge branch 'main' into patch-ios-stretched-header-fix
This commit is contained in:
commit
c4b27926ed
10 changed files with 441 additions and 260 deletions
|
@ -284,6 +284,7 @@ img {
|
|||
}
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
.bars-wrap {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
|
@ -305,7 +306,10 @@ img {
|
|||
animation-delay: 0.5s;
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
.blink[data-loading='true'] {
|
||||
animation: blink infinite ease 1s;
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.5s ease;
|
||||
|
|
|
@ -4,9 +4,9 @@ defineEmits(['click']);
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
:class="'bi bi-' + (ico ? ico : 'play')"
|
||||
@click="$emit('click')"></button>
|
||||
<button :class="'bi bi-' + (ico ? ico : 'play')" @click="$emit('click')">
|
||||
<slot name="menu"></slot>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -6,7 +6,11 @@ import Modal from './Modal.vue';
|
|||
|
||||
import { useRand } from '@/scripts/colors.js';
|
||||
import { useStore } from '@/scripts/util.js';
|
||||
import { getJsonAuth, getAuthPlaylists } from '@/scripts/fetch.js';
|
||||
import {
|
||||
useAuthCreatePlaylist,
|
||||
getAuthPlaylists,
|
||||
getJsonAuth,
|
||||
} from '@/scripts/fetch.js';
|
||||
import { useI18n } from '@/stores/misc.js';
|
||||
|
||||
import {
|
||||
|
@ -121,16 +125,7 @@ const Login = async () => {
|
|||
},
|
||||
createPlaylist = async () => {
|
||||
if (text.value) {
|
||||
const res = await getJsonAuth('/user/playlists/create', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
name: `Playlist - ${text.value}`,
|
||||
}),
|
||||
headers: {
|
||||
Authorization: store.auth,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const res = await useAuthCreatePlaylist(text.value);
|
||||
|
||||
getPlaylists();
|
||||
show.new = false;
|
||||
|
|
|
@ -22,9 +22,33 @@ const { t } = useI18n(),
|
|||
const emit = defineEmits(['play-urls']),
|
||||
filters = ['music_songs', 'music_albums', 'music_artists'],
|
||||
filter = ref('music_songs'),
|
||||
isSearch = ref(/search/.test(location.pathname));
|
||||
isSearch = ref(/search/.test(location.pathname)),
|
||||
albumMenu = ref(false);
|
||||
|
||||
const saveAlbum = () => {
|
||||
const shuffleAdd = () => {
|
||||
const songs = results.items.songs.items.map(i => ({
|
||||
url: i.url,
|
||||
title: i.title,
|
||||
thumbnails: [{ url: i.thumbnail }],
|
||||
})),
|
||||
copy = [];
|
||||
|
||||
let nos = songs.length;
|
||||
|
||||
while (nos) {
|
||||
const i = Math.floor(Math.random() * nos--);
|
||||
|
||||
copy.push(songs[i]);
|
||||
|
||||
songs[i] = songs[nos];
|
||||
delete songs[nos];
|
||||
}
|
||||
|
||||
console.log(songs, copy);
|
||||
|
||||
emit('play-urls', copy);
|
||||
},
|
||||
saveAlbum = () => {
|
||||
const urls = results.items?.songs?.items?.map(item => ({
|
||||
url: item.url,
|
||||
title: item.title,
|
||||
|
@ -112,20 +136,32 @@ onUpdated(() => {
|
|||
})),
|
||||
)
|
||||
" />
|
||||
<Btn ico="star" @click="saveAlbum" />
|
||||
<Btn
|
||||
ico="plus-lg"
|
||||
@click="
|
||||
data.state.urls.push(
|
||||
...results.items.songs.items.map(i => ({
|
||||
url: i.url,
|
||||
title: i.title,
|
||||
thumbnails: [{ url: i.thumbnail }],
|
||||
})),
|
||||
)
|
||||
" />
|
||||
|
||||
<span>{{ results.items?.songs?.title }}</span>
|
||||
<Btn ico="three-dots" @click="albumMenu = !albumMenu">
|
||||
<template #menu>
|
||||
<Transition name="fade">
|
||||
<div v-if="albumMenu" class="alb popup">
|
||||
<button class="bi bi-bookmark-plus" @click="saveAlbum"></button>
|
||||
|
||||
<button
|
||||
class="bi bi-plus-lg"
|
||||
@click="
|
||||
data.state.urls.push(
|
||||
...results.items.songs.items.map(i => ({
|
||||
url: i.url,
|
||||
title: i.title,
|
||||
thumbnails: [{ url: i.thumbnail }],
|
||||
})),
|
||||
)
|
||||
"></button>
|
||||
|
||||
<button class="bi bi-shuffle" @click="shuffleAdd"></button>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
</Btn>
|
||||
|
||||
<span class="ml-auto">{{ results.items?.songs?.title }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="isSearch" class="filters">
|
||||
|
@ -146,8 +182,9 @@ onUpdated(() => {
|
|||
class="search-songs">
|
||||
<h2 v-if="!isSearch">{{ t('title.songs') }}</h2>
|
||||
<div class="grid">
|
||||
<template v-for="song in results.items.songs.items">
|
||||
<template v-for="(song, index) in results.items.songs.items">
|
||||
<SongItem
|
||||
:index="index"
|
||||
:author="song.uploaderName || song.subtitle"
|
||||
:title="song.title || song.name"
|
||||
:channel="song.uploaderUrl || song.subId"
|
||||
|
@ -264,10 +301,18 @@ onUpdated(() => {
|
|||
margin-right: 0;
|
||||
}
|
||||
:deep(.bi-play) {
|
||||
margin-right: 1rem;
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
:deep(.bi-plus-lg) {
|
||||
margin-right: auto;
|
||||
:deep(.ml-auto) {
|
||||
margin-left: auto;
|
||||
}
|
||||
.alb {
|
||||
bottom: 2.5rem;
|
||||
border-radius: 0.25rem;
|
||||
box-shadow: -0.5rem 0.5rem 1rem var(--color-shadow);
|
||||
}
|
||||
.alb .bi {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
.filters {
|
||||
display: flex;
|
||||
|
|
|
@ -1,24 +1,28 @@
|
|||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
|
||||
import { getJsonAuth } from '@/scripts/fetch.js';
|
||||
import { useRand } from '@/scripts/colors.js';
|
||||
import { useStore } from '@/scripts/util.js';
|
||||
|
||||
import { useArtist } from '@/stores/results.js';
|
||||
import { useArtist, useResults } from '@/stores/results.js';
|
||||
import { useData, usePlayer } from '@/stores/player.js';
|
||||
|
||||
const rand = useRand(),
|
||||
data = useData(),
|
||||
player = usePlayer(),
|
||||
artist = useArtist();
|
||||
artist = useArtist(),
|
||||
{ playlistId } = useResults();
|
||||
|
||||
const props = defineProps({
|
||||
index: Number,
|
||||
author: String,
|
||||
title: String,
|
||||
channel: String,
|
||||
play: String,
|
||||
art: String,
|
||||
}),
|
||||
emit = defineEmits(['open-song']),
|
||||
emit = defineEmits(['open-song', 'remove']),
|
||||
show = ref(false);
|
||||
|
||||
const openSong = el => {
|
||||
|
@ -43,6 +47,24 @@ const openSong = el => {
|
|||
)
|
||||
emit('open-song', props.play);
|
||||
},
|
||||
Remove = () => {
|
||||
// WIP
|
||||
|
||||
const auth = useStore().getItem('auth');
|
||||
|
||||
if (auth) {
|
||||
getJsonAuth('/user/playlists/remove', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: auth,
|
||||
},
|
||||
body: {
|
||||
index,
|
||||
playlistId,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
Share = async () => {
|
||||
if ('share' in navigator) {
|
||||
const data = {
|
||||
|
@ -59,7 +81,7 @@ const openSong = el => {
|
|||
} else {
|
||||
navigator.clipboard.writeText(location.host + props.play).then(
|
||||
() => {
|
||||
console.log('Copied to Clipboard');
|
||||
alert('Copied to Clipboard');
|
||||
},
|
||||
err => {
|
||||
console.log(err);
|
||||
|
@ -79,12 +101,8 @@ onMounted(() => {
|
|||
<span class="flex content">
|
||||
<h4>{{ title }}</h4>
|
||||
<a
|
||||
:href="channel != '[]' ? channel : ''"
|
||||
@click.prevent="
|
||||
channel != '[]'
|
||||
? artist.getArtist(channel.replace('/channel/', ''))
|
||||
: ''
|
||||
"
|
||||
:href="channel"
|
||||
@click.prevent="artist.getArtist(channel.replace('/channel/', ''))"
|
||||
class="ign">
|
||||
<i class="ign">{{ author ? author.replaceAll(' - Topic', '') : '' }}</i>
|
||||
</a>
|
||||
|
@ -96,6 +114,12 @@ onMounted(() => {
|
|||
@mouseleave="show = false">
|
||||
<Transition name="fade">
|
||||
<div v-if="show" class="popup ign">
|
||||
<!-- TODO: Check if user is admin -->
|
||||
<span
|
||||
v-if="useStore().auth && playlistId"
|
||||
class="bi bi-dash-lg ign"
|
||||
@click="Remove"></span>
|
||||
|
||||
<span class="bi bi-plus-lg ign" @click="addSong"></span>
|
||||
|
||||
<span class="bi bi-share ign" @click="Share"></span>
|
||||
|
@ -135,4 +159,7 @@ span.bi-three-dots-vertical {
|
|||
width: 120px;
|
||||
height: 120px;
|
||||
}
|
||||
.bi-dash-lg {
|
||||
color: indianred;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -5,7 +5,11 @@ import Modal from './Modal.vue';
|
|||
|
||||
import { useStore } from '@/scripts/util.js';
|
||||
import { useListPlaylists, useUpdatePlaylist } from '@/scripts/db.js';
|
||||
import { getAuthPlaylists, getJsonAuth } from '@/scripts/fetch.js';
|
||||
import {
|
||||
getAuthPlaylists,
|
||||
useAuthCreatePlaylist,
|
||||
useAuthAddToPlaylist,
|
||||
} from '@/scripts/fetch.js';
|
||||
|
||||
import { useData, usePlayer } from '@/stores/player.js';
|
||||
import { useI18n } from '@/stores/misc.js';
|
||||
|
@ -24,7 +28,9 @@ const emit = defineEmits(['save']),
|
|||
pl = ref(''),
|
||||
list = ref([]),
|
||||
remote = ref([]),
|
||||
plRemote = ref(false);
|
||||
plRemote = ref(false),
|
||||
liked = ref(undefined),
|
||||
liking = ref(false);
|
||||
|
||||
function List() {
|
||||
showme.pl = true;
|
||||
|
@ -41,18 +47,7 @@ function List() {
|
|||
function Save() {
|
||||
if (pl.value) {
|
||||
if (plRemote.value == true && store.auth) {
|
||||
getJsonAuth('/user/playlists/add', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: store.auth,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
playlistId: pl.value,
|
||||
videoId: new URL(
|
||||
'https://example.com' + data.state.url,
|
||||
).searchParams.get('v'),
|
||||
}),
|
||||
});
|
||||
setAuthAddToPlaylist(data.state.url);
|
||||
} else if (plRemote.value == false) {
|
||||
useUpdatePlaylist(
|
||||
pl.value,
|
||||
|
@ -69,6 +64,26 @@ function Save() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function Like() {
|
||||
liking.value = true;
|
||||
|
||||
remote.value = await getAuthPlaylists();
|
||||
|
||||
let fav = remote.value.filter(i => i.name == 'Playlist - Favorites')[0];
|
||||
|
||||
if (!fav) {
|
||||
const { playlistId } = await useAuthCreatePlaylist('Favorites');
|
||||
|
||||
fav = { id: playlistId };
|
||||
}
|
||||
|
||||
const { message } = await useAuthAddToPlaylist(fav.id, data.state.url);
|
||||
|
||||
if (message == 'ok') liked.value = data.state.url;
|
||||
|
||||
liking.value = false;
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
|
@ -183,12 +198,24 @@ function Save() {
|
|||
aria-label="Show Information About Song"
|
||||
:data-active="player.state.info"
|
||||
@click="player.toggle('info')"></button>
|
||||
|
||||
<button
|
||||
v-if="store.auth"
|
||||
id="like-btn"
|
||||
title="Add song to favorites"
|
||||
class="bi blink"
|
||||
:class="data.state.url == liked ? 'bi-heart-fill' : 'bi-heart'"
|
||||
:data-active="data.state.url == liked"
|
||||
:data-loading="liking"
|
||||
@click="Like"></button>
|
||||
|
||||
<button
|
||||
id="addToPlaylist"
|
||||
title="Add Current Song to a Playlist"
|
||||
aria-label="Add Current Song to a Playlist"
|
||||
class="bi bi-collection"
|
||||
@click="List"></button>
|
||||
|
||||
<button
|
||||
id="list-btn"
|
||||
title="Current Playlist"
|
||||
|
@ -196,11 +223,13 @@ function Save() {
|
|||
class="bi bi-music-note-list"
|
||||
:data-active="player.state.playlist"
|
||||
@click="player.toggle('playlist')"></button>
|
||||
|
||||
<button
|
||||
id="btn-lyrics"
|
||||
class="bi bi-file-music"
|
||||
:data-active="player.state.lyrics"
|
||||
@click="player.toggle('lyrics')"></button>
|
||||
|
||||
<button
|
||||
id="loop-btn"
|
||||
title="Loop"
|
||||
|
|
|
@ -49,17 +49,57 @@ export async function getJsonHyp(path) {
|
|||
export async function getJsonAuth(path, opts) {
|
||||
const root = useStore().getItem('authapi') || 'pipedapi.kavin.rocks';
|
||||
|
||||
return await fetch('https://' + root + path, opts).then(res => res.json());
|
||||
return await fetch('https://' + root + path, opts)
|
||||
.then(res => res.json())
|
||||
.catch(err => {
|
||||
alert(err);
|
||||
});
|
||||
}
|
||||
|
||||
export async function useAuthCreatePlaylist(name) {
|
||||
const auth = useStore().getItem('auth');
|
||||
|
||||
if (auth && name) {
|
||||
return await getJsonAuth('/user/playlists/create', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
name: `Playlist - ${name}`,
|
||||
}),
|
||||
headers: {
|
||||
Authorization: auth,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAuthPlaylists() {
|
||||
if (!!useStore().getItem('auth')) {
|
||||
const auth = useStore().getItem('auth');
|
||||
|
||||
if (auth) {
|
||||
const res = await getJsonAuth('/user/playlists', {
|
||||
headers: {
|
||||
Authorization: useStore().getItem('auth'),
|
||||
Authorization: auth,
|
||||
},
|
||||
});
|
||||
|
||||
return res.filter(i => i.name.startsWith('Playlist - '));
|
||||
}
|
||||
}
|
||||
|
||||
export async function useAuthAddToPlaylist(id, path) {
|
||||
const auth = useStore().getItem('auth');
|
||||
|
||||
if (auth && id && path) {
|
||||
return getJsonAuth('/user/playlists/add', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: auth,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
playlistId: id,
|
||||
videoId: new URL('https://example.com' + path).searchParams.get('v'),
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,8 @@ import { useRoute } from '@/scripts/util.js';
|
|||
|
||||
export const useResults = defineStore('results', () => {
|
||||
const items = ref({}),
|
||||
search = ref('');
|
||||
search = ref(''),
|
||||
playlistId = ref('');
|
||||
|
||||
function setItem(key, val) {
|
||||
items.value[key] = val;
|
||||
|
@ -20,13 +21,14 @@ export const useResults = defineStore('results', () => {
|
|||
for (let i in items.value) {
|
||||
items.value[i] = undefined;
|
||||
}
|
||||
playlistId.value = '';
|
||||
}
|
||||
|
||||
async function getExplore() {
|
||||
const json = await getJsonHyp('/explore');
|
||||
|
||||
console.log(json);
|
||||
useArtist().reset();
|
||||
resetItems();
|
||||
|
||||
setItem('songs', { items: json.trending });
|
||||
setItem('albums', { items: json.albums_and_singles });
|
||||
|
@ -41,6 +43,14 @@ export const useResults = defineStore('results', () => {
|
|||
console.log(json, json.relatedStreams);
|
||||
|
||||
resetItems();
|
||||
|
||||
if (
|
||||
/[\da-fA-F]{8}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{12}/.test(
|
||||
hash,
|
||||
)
|
||||
)
|
||||
playlistId.value = hash;
|
||||
|
||||
setItem('songs', {
|
||||
items: json.relatedStreams,
|
||||
title: json.name,
|
||||
|
@ -48,11 +58,17 @@ export const useResults = defineStore('results', () => {
|
|||
|
||||
useRoute(e);
|
||||
useNav().state.page = 'home';
|
||||
|
||||
useArtist().reset();
|
||||
}
|
||||
|
||||
return { items, search, setItem, resetItems, getExplore, getAlbum };
|
||||
return {
|
||||
items,
|
||||
search,
|
||||
playlistId,
|
||||
setItem,
|
||||
resetItems,
|
||||
getExplore,
|
||||
getAlbum,
|
||||
};
|
||||
});
|
||||
|
||||
export const useArtist = defineStore('artist', () => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue