Switched to Composition API

This commit is contained in:
Shiny Nematoda 2022-04-15 15:29:43 +05:30
parent 75b0a9b319
commit 8ee03fa623
11 changed files with 705 additions and 647 deletions

View file

@ -45,6 +45,8 @@ npm run build
Please refer to LICENSE. Please refer to LICENSE.
You can reach out to me on <TODO>
### Dependencies and Mentions ### Dependencies and Mentions
- VueJS -> [MIT][vue] - VueJS -> [MIT][vue]

View file

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref, reactive, onMounted } from 'vue';
import NavBar from './components/NavBar.vue'; import NavBar from './components/NavBar.vue';
import StatusBar from './components/StatusBar.vue'; import StatusBar from './components/StatusBar.vue';
@ -8,7 +8,310 @@ import Search from './components/Search.vue';
import Playlists from './components/Playlists.vue'; import Playlists from './components/Playlists.vue';
import Artist from './components/Artist.vue'; import Artist from './components/Artist.vue';
let search = ref(''); import { getJson, getJsonPiped } from './scripts/fetch.js'
const data = reactive({
artUrl: '',
cover: '',
audioSrc: '',
url: '',
urls: [],
songItems: null,
hls: null,
items: {},
title: '',
artist: '',
state: 'play',
duration: 0,
time: 0,
showplaylist: false,
loop: false,
});
const artist = reactive({
playlistId: null,
title: null,
description: null,
subscriberCount: 0,
thumbnails: [],
});
const search = ref('');
const audio = ref(null);
function parseUrl() {
const loc = location.href.split('/');
console.log(loc);
switch (loc[3].replace(location.search, '')) {
case '':
case 'search':
search.value = loc[4];
console.log(search.value);
break;
case 'watch':
getSong(loc[3]);
console.log(loc[3]);
break;
case 'playlist':
getAlbum(loc[3]);
console.log(loc[3]);
break;
case 'channel':
getArtist(loc[4]);
console.log(loc[4]);
default:
console.log(loc);
}
}
function Toggle(e) {
console.log(e, data[e]);
if (data[e]) {
data[e] = false;
} else {
data[e] = true;
}
}
function Update(e) {
search.value = e;
console.log('update');
}
function timeUpdate(t) {
data.time = Math.floor( (t / data.duration) * 100 );
}
function setTime(t) {
audio.value.currentTime = (t / 100) * data.duration;
}
function setSong(s) {
data.urls = [s];
playNext();
}
function addSong(s) {
data.urls.push(s);
const index = data.urls.map((s) => s.url).indexOf(data.url);
if (
(index == data.urls.length - 1 && data.time > 98) ||
data.urls.length == 1
) {
console.log(true);
playNext();
} else {
console.log(false);
}
console.log(s, data.urls);
}
function playThis(t) {
const i = data.urls.indexOf(t);
getSong(data.urls[i].url);
}
function playList(a) {
data.urls = a;
getSong(data.urls[0].url);
}
function playNext(u) {
if (data.hls) {
data.hls.destroy();
}
const i = data.urls.map((s) => s.url).indexOf(data.url),
next = data.urls[i + 1];
console.log('Index: ' + i);
console.log(data.url, data.urls, next);
if (data.urls.length > i && data.urls.length != 0 && next) {
getSong(next.url);
} else if (data.loop) {
data.url = data.urls[0].url;
getSong(data.urls[0].url);
} else {
data.urls = [];
}
}
async function getSong(e) {
console.log(e);
const hash = new URLSearchParams(e.substring(e.indexOf('?'))).get('v'),
json = await getJsonPiped('/streams/' + hash);
console.log(json);
Stream({
art: json.thumbnailUrl,
artist: json.uploader,
time: json.duration,
hls: json.hls,
stream: json.audioStreams[0].url,
title: json.title,
url: e,
});
}
async function getAlbum(e) {
console.log('Album: ', e);
const hash = new URLSearchParams(e.substring(e.indexOf('?'))).get('list'),
json = await getJsonPiped('/playlists/' + hash);
console.log(json, json.relatedStreams);
data.songItems = {
items: json.relatedStreams,
title: json.name,
};
history.pushState({}, '', e);
for (let i in artist) {
artist[i] = null
}
}
async function getArtist(e) {
console.log(e);
const json = await getJson(
'https://hyperpipeapi.onrender.com/channel/' + e,
);
console.log(json);
data.items = json.items;
data.items.notes = json.playlistId;
json.items = null;
for(let i in json) {
artist[i] = json[i];
};
history.pushState({}, '', '/channel/' + e);
}
function setVolume(vol) {
audio.value.volume = vol;
}
function playPause() {
if (audio.value.paused) {
audio.value.play();
data.state = 'pause';
} else {
audio.value.pause();
data.state = 'play';
}
}
function Stream(res) {
console.log(res);
data.art = res.art;
data.cover = `--art: url(${res.art});`;
data.nowtitle = res.title;
data.nowartist = res.artist.split(' - ')[0];
data.duration = res.time;
data.url = res.url;
if (!!Hls && Hls.isSupported()) {
data.hls = new Hls();
console.log(data.hls.levels);
data.hls.loadSource(res.hls);
data.hls.attachMedia(audio.value);
} else {
data.audioSrc = res.stream;
}
}
function audioCanPlay() {
lazyLoad();
audio.value.play();
data.state = 'pause';
if (location.pathname != '/playlist') {
history.pushState({}, '', data.url);
}
document.title = `Playing: ${data.nowtitle} by ${data.nowartist}`;
setMetadata(data.art);
}
function setMetadata(art) {
if (navigator.mediaSession) {
navigator.mediaSession.metadata = new MediaMetadata({
title: data.nowtitle,
artist: data.nowartist,
artwork: [
{
src: art,
type: 'image/png',
},
],
});
}
}
function lazyLoad() {
let lazyElems;
if ('IntersectionObserver' in window) {
lazyElems = document.querySelectorAll('.bg-img:not(.lazy)');
let imgObs = new IntersectionObserver((elems, obs) => {
elems.forEach((elem) => {
setTimeout(() => {
if (elem.isIntersecting) {
let ele = elem.target;
ele.classList.add('lazy');
imgObs.unobserve(ele);
}
}, 20);
});
});
lazyElems.forEach((img) => {
imgObs.observe(img);
});
} else {
console.log('Failed');
}
}
onMounted(() => {
lazyLoad();
document.addEventListener('scroll', lazyLoad);
document.addEventListener('resize', lazyLoad);
document.addEventListener('orientationChange', lazyLoad);
window.addEventListener('popstate', parseUrl);
window.onbeforeunload = () => {
return 'Are you Sure?';
};
console.log(audio.value)
parseUrl();
console.log('Mounted <App>!');
});
</script> </script>
<template> <template>
@ -31,355 +334,52 @@ let search = ref('');
</template> </template>
<header v-if="!artist.title"> <header v-if="!artist.title">
<div v-if="cover" class="art bg-img" :style="cover"></div> <div v-if="data.cover" class="art bg-img" :style="data.cover"></div>
<div class="wrapper"> <div class="wrapper">
<NowPlaying :title="nowtitle" :artist="nowartist" /> <NowPlaying :title="data.nowtitle" :artist="data.nowartist" />
</div> </div>
</header> </header>
<main class="placeholder"> <main class="placeholder">
<Search <Search
@get-song="setSong"
@get-album="getAlbum" @get-album="getAlbum"
@get-artist="getArtist" @get-artist="getArtist"
@lazy="lazyLoad" @lazy="lazyLoad"
@play-urls="playList" @play-urls="playList"
@add-song="addSong" @add-song="addSong"
:items="items" :items="data.items"
:songItems="songItems" :songItems="data.songItems"
:search="search" /> :search="search" />
</main> </main>
<Playlists <Playlists
@playthis="playThis" @playthis="playThis"
:url="url" :url="data.url"
:urls="urls" :urls="data.urls"
:show="showplaylist" /> :show="data.showplaylist" />
<StatusBar <StatusBar
@play="playPause" @play="playPause"
@vol="setVolume" @vol="setVolume"
@list="Toggle" @list="Toggle"
@loop="Toggle" @loop="Toggle"
:state="state" @change-time="setTime"
:time="time" :state="data.state"
:show="showplaylist" :time="data.time"
:loop="loop" /> :show="data.showplaylist"
:loop="data.loop" />
<audio <audio
id="audio" id="audio"
ref="audio" ref="audio"
:src="audioSrc" :src="data.audioSrc"
@canplay="audioCanPlay" @canplay="audioCanPlay"
@timeupdate="timeUpdate($event.target.currentTime)" @timeupdate="timeUpdate($event.target.currentTime)"
@ended="playNext" @ended="playNext"
autoplay></audio> autoplay></audio>
</template> </template>
<script>
export default {
data() {
return {
artUrl: '',
cover: '',
nowtitle: '',
nowartist: '',
state: 'play',
audioSrc: '',
duration: 0,
time: 0,
url: '',
urls: [],
songItems: null,
showplaylist: false,
loop: false,
hls: null,
artist: {
playlistId: null,
title: null,
description: null,
subscriberCount: 0,
thumbnails: [],
},
items: {},
};
},
mounted() {
this.lazyLoad();
document.addEventListener('scroll', this.lazyLoad);
document.addEventListener('resize', this.lazyLoad);
document.addEventListener('orientationChange', this.lazyLoad);
window.addEventListener('popstate', this.parseUrl);
window.onbeforeunload = () => {
return 'Are you Sure?';
};
this.parseUrl();
console.log('Mounted <App>!');
},
methods: {
parseUrl() {
const loc = location.href.split('/');
console.log(loc);
switch (loc[3].replace(location.search, '')) {
case '':
case 'search':
this.search = loc[4];
console.log(loc[4], this.search);
break;
case 'watch':
this.getSong(loc[3]);
console.log(loc[3]);
break;
case 'playlist':
this.getAlbum(loc[3]);
console.log(loc[3]);
break;
case 'channel':
this.getArtist(loc[4]);
console.log(loc[4]);
default:
console.log(loc);
}
},
Toggle(e) {
console.log(this[e]);
if (this[e]) {
this[e] = false;
} else {
this[e] = true;
}
},
Update(e) {
this.search = e;
console.log('update');
},
timeUpdate(t) {
this.time = Math.floor((t / this.duration) * 100);
},
async getJson(url) {
const res = await fetch(url).then((res) => res.json());
if (!res.error) {
return res;
} else {
alert(
res.message
.replaceAll('Video', 'Audio')
.replaceAll('video', 'audio')
.replaceAll('watched', 'heard'),
);
console.error(res.message);
}
},
setSong(s) {
this.urls = [s];
this.playNext();
},
addSong(s) {
this.urls.push(s);
const index = this.urls.map((s) => s.url).indexOf(this.url);
if (
(index == this.urls.length - 1 && this.time > 98) ||
this.urls.length == 1
) {
console.log(true);
this.playNext();
} else {
console.log(false);
}
console.log(s, this.urls);
},
playThis(t) {
const i = this.urls.indexOf(t);
this.getSong(this.urls[i].url);
},
playList(a) {
this.urls = a;
this.getSong(this.urls[0].url);
},
playNext(u) {
if (this.hls) {
this.hls.destroy();
}
const i = this.urls.map((s) => s.url).indexOf(this.url),
next = this.urls[i + 1];
console.log('Index: ' + i);
console.log(this.url, this.urls, next);
if (this.urls.length > i && this.urls.length != 0 && next) {
this.getSong(next.url);
} else if (this.loop) {
this.url = this.urls[0].url;
this.getSong(this.urls[0].url);
} else {
this.urls = [];
}
},
async getSong(e) {
console.log(e);
const hash = new URLSearchParams(e.substring(e.indexOf('?'))).get('v'),
json = await this.getJson(
'https://pipedapi.kavin.rocks/streams/' + hash,
);
console.log(json);
this.Stream({
art: json.thumbnailUrl,
artist: json.uploader,
time: json.duration,
hls: json.hls,
stream: json.audioStreams[0].url,
title: json.title,
url: e,
});
},
async getAlbum(e) {
console.log('Album: ', e);
const hash = new URLSearchParams(e.substring(e.indexOf('?'))).get('list'),
json = await this.getJson(
'https://pipedapi.kavin.rocks/playlists/' + hash,
);
console.log(json, json.relatedStreams);
this.songItems = {
items: json.relatedStreams,
title: json.name,
};
history.pushState({}, '', e);
this.artist = {
playlistId: null,
title: null,
description: null,
subscriberCount: 0,
thumbnails: [],
};
},
async getArtist(e) {
console.log(e);
const json = await this.getJson(
'https://hyperpipeapi.onrender.com/channel/' + e,
);
console.log(json);
this.items = json.items;
this.items.notes = json.playlistId;
json.items = null;
this.artist = json;
history.pushState({}, '', '/channel/' + e);
},
setVolume(vol) {
this.$refs.audio.volume = vol;
},
playPause() {
if (this.$refs.audio.paused) {
this.$refs.audio.play();
this.state = 'pause';
} else {
this.$refs.audio.pause();
this.state = 'play';
}
},
Stream(res) {
console.log(res);
this.art = res.art;
this.cover = `--art: url(${res.art});`;
this.nowtitle = res.title;
this.nowartist = res.artist.split(' - ')[0];
this.duration = res.time;
this.url = res.url;
if (!!Hls && Hls.isSupported()) {
this.hls = new Hls();
console.log(this.hls.levels);
this.hls.loadSource(res.hls);
this.hls.attachMedia(this.$refs.audio);
} else {
this.audioSrc = res.stream;
}
},
audioCanPlay() {
this.lazyLoad();
this.$refs.audio.play();
this.state = 'pause';
if (location.pathname != '/playlist') {
history.pushState({}, '', this.url);
}
document.title = `Playing: ${this.nowtitle} by ${this.nowartist}`;
this.setMetadata(this.art);
},
setMetadata(art) {
if (navigator.mediaSession) {
navigator.mediaSession.metadata = new MediaMetadata({
title: this.nowtitle,
artist: this.nowartist,
artwork: [
{
src: art,
type: 'image/png',
},
],
});
}
},
lazyLoad() {
let lazyElems;
if ('IntersectionObserver' in window) {
lazyElems = document.querySelectorAll('.bg-img:not(.lazy)');
let imgObs = new IntersectionObserver((elems, obs) => {
elems.forEach((elem) => {
setTimeout(() => {
if (elem.isIntersecting) {
let ele = elem.target;
ele.classList.add('lazy');
imgObs.unobserve(ele);
}
}, 20);
});
});
lazyElems.forEach((img) => {
imgObs.observe(img);
});
} else {
console.log('Failed');
}
},
},
};
</script>
<style> <style>
@import './assets/base.css'; @import './assets/base.css';
@ -405,22 +405,7 @@ header {
height: 175px; height: 175px;
width: 175px; width: 175px;
} }
.bg-img {
background-image: linear-gradient(45deg, #88c0d0, #5e81ac);
background-position: center;
background-size: cover;
border-radius: 0.25rem;
}
.bg-img.lazy {
background-image: var(--art);
}
.search-artists {
text-align: center;
}
.search-artists .bg-img {
border-radius: 50%;
margin-bottom: 0.5rem;
}
img, img,
.card, .card,
.card-bg { .card-bg {
@ -436,21 +421,6 @@ a,
color: var(--color-foreground); color: var(--color-foreground);
transition: 0.4s; transition: 0.4s;
} }
button {
border: none;
background: transparent;
color: var(--color-text);
appearence: none;
}
.bi {
color: var(--color-text);
font-size: 1.25rem;
transition: color 0.3s ease;
}
.bi:hover,
.bi.true {
color: var(--color-foreground);
}
.flex { .flex {
display: flex; display: flex;
align-items: center; align-items: center;
@ -463,49 +433,7 @@ button {
.flex .bi { .flex .bi {
line-height: 0; line-height: 0;
} }
.caps {
text-transform: capitalize;
}
.pop {
--shadow: none;
--translate: 0;
}
.pop,
.pop-2 {
transition: box-shadow 0.4s ease, transform 0.4s ease;
}
.pop:hover {
--shadow: 0.5rem 0.5rem 1rem var(--color-shadow);
--translate: -1.25rem;
box-shadow: var(--shadow);
transform: translateX(calc(var(--translate) / 2))
translateY(calc(var(--translate) / 2));
}
.pop-2 {
transform: translateX(var(--translate)) translateY(var(--translate));
box-shadow: var(--shadow);
}
.popup-wrap {
--display: none;
position: relative;
}
.popup-wrap:hover,
.popup-wrap:focus,
.popup:focus,
.popup:active {
--display: flex;
}
.popup {
position: absolute;
display: var(--display);
background-color: var(--color-background);
padding: 0.5rem;
border-radius: 0.125rem;
z-index: 999;
bottom: 1.25rem;
box-shadow: 0 0 0.5rem var(--color-border);
animation: fade 0.4s ease;
}
@media (hover: hover) { @media (hover: hover) {
a:hover { a:hover {
background-color: var(--color-border); background-color: var(--color-border);
@ -532,21 +460,4 @@ button {
margin: 0 2rem 0 0; margin: 0 2rem 0 0;
} }
} }
@keyframes fade {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fill {
from {
width: 0;
}
to {
width: var(--width);
}
}
</style> </style>

View file

@ -114,6 +114,129 @@ body {
height: 0.75rem; height: 0.75rem;
} }
h2 { *:focus {
text-align: center; outline: none;
}
button {
border: none;
background: transparent;
color: var(--color-text);
appearence: none;
}
/* Components and Helpers */
.bi {
color: var(--color-text);
font-size: 1.25rem;
transition: color 0.3s ease;
}
.bi:hover,
.bi.true {
color: var(--color-foreground);
}
.caps {
text-transform: capitalize;
}
.bg-img {
background-image: linear-gradient(45deg, #88c0d0, #5e81ac);
background-position: center;
background-size: cover;
border-radius: 0.25rem;
}
.bg-img.lazy {
background-image: var(--art);
}
.pop {
--shadow: none;
--translate: 0;
}
.pop,
.pop-2 {
transition: box-shadow 0.4s ease, transform 0.4s ease;
}
.pop:hover {
--shadow: 0.5rem 0.5rem 1rem var(--color-shadow);
--translate: -1.25rem;
box-shadow: var(--shadow);
transform: translateX(calc(var(--translate) / 2))
translateY(calc(var(--translate) / 2));
}
.pop-2 {
transform: translateX(var(--translate)) translateY(var(--translate));
box-shadow: var(--shadow);
}
.popup-wrap {
--display: none;
position: relative;
}
.popup-wrap:hover,
.popup-wrap:focus,
.popup:focus,
.popup:active {
--display: flex;
}
.popup {
position: absolute;
display: var(--display);
background-color: var(--color-background);
padding: 0.5rem;
border-radius: 0.125rem;
z-index: 999;
bottom: 1.25rem;
box-shadow: 0 0 0.5rem var(--color-border);
animation: fade 0.4s ease;
}
.bars-wrap {
position: absolute;
height: 1.5rem;
width: 2rem;
transform: rotateZ(180deg);
}
.bars {
position: relative;
height: 15%;
width: calc(calc(100% / 3) - 0.2rem);
margin-left: 0.1rem;
background: var(--color-foreground);
float: left;
animation: heightc 1s ease infinite;
}
.bars:first-child {
animation-delay: 0.25s;
}
.bars:nth-child(2) {
animation-delay: 0.5s;
}
.bars:last-child {
margin-left: none;
}
/* Animations */
@keyframes fade {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fill {
from {
width: 0;
}
to {
width: var(--width);
}
}
@keyframes heightc {
50% {
height: 100%;
}
} }

View file

@ -7,6 +7,12 @@ defineProps({
default: '--art: linear-gradient(45deg, #88c0d0, #5e81ac);', default: '--art: linear-gradient(45deg, #88c0d0, #5e81ac);',
}, },
}); });
const emit = defineEmits(['open-album']);
function onClick() {
emit('open-album');
}
</script> </script>
<template> <template>
@ -20,16 +26,6 @@ defineProps({
</div> </div>
</template> </template>
<script>
export default {
methods: {
onClick() {
this.$emit('open-album');
},
},
};
</script>
<style scoped> <style scoped>
.card { .card {
min-height: 17rem; min-height: 17rem;

View file

@ -1,5 +1,4 @@
<script setup> <script setup>
import { ref } from 'vue';
import PlayBtn from './PlayBtn.vue'; import PlayBtn from './PlayBtn.vue';
defineProps(['title', 'desc', 'subs', 'thumbs', 'play']); defineProps(['title', 'desc', 'subs', 'thumbs', 'play']);

View file

@ -1,6 +1,7 @@
<script setup> <script setup>
defineEmits(['update-search']);
import SearchBar from '../components/SearchBar.vue'; import SearchBar from '../components/SearchBar.vue';
defineEmits(['update-search']);
</script> </script>
<template> <template>

View file

@ -1,5 +1,9 @@
<script setup> <script setup>
defineProps(['url', 'urls', 'show']); defineProps({
url: String,
urls: Array,
show: Boolean,
});
defineEmits(['playthis']); defineEmits(['playthis']);
</script> </script>
@ -56,34 +60,4 @@ defineEmits(['playthis']);
.pl-main { .pl-main {
padding-left: 3rem; padding-left: 3rem;
} }
.bars-wrap {
position: absolute;
height: 1.5rem;
width: 2rem;
transform: rotateZ(180deg);
}
.bars {
position: relative;
height: 15%;
width: calc(calc(100% / 3) - 0.2rem);
margin-left: 0.1rem;
background: var(--color-foreground);
float: left;
animation: heightc 1s ease infinite;
}
.bars:first-child {
animation-delay: 0.25s;
}
.bars:nth-child(2) {
animation-delay: 0.5s;
}
.bars:last-child {
margin-left: none;
}
@keyframes heightc {
50% {
height: 100%;
}
}
</style> </style>

View file

@ -1,44 +1,141 @@
<script setup> <script setup>
defineProps([ import { reactive, watch } from 'vue';
'search', import PlayBtn from './PlayBtn.vue';
'songItems', import SongItem from './SongItem.vue';
'items' import AlbumItem from './AlbumItem.vue';
]);
defineEmits([ import { getJsonPiped, getPipedQuery } from '../scripts/fetch.js'
'get-song',
const props = defineProps(['search', 'songItems', 'items']);
const emit = defineEmits([
'get-album', 'get-album',
'get-artist', 'get-artist',
'lazy', 'lazy',
'play-urls', 'play-urls',
'add-song', 'add-song',
]); ]);
const data = reactive({
notes: null,
albums: null,
albumTitle: null,
songs: null,
recommendedArtists: null,
});
function Reset() {
for (let i in data) {
data[i] = null
}
}
function playAlbum() {
const urls = data.songs.items.map((item) => {
return { url: item.url, title: item.title };
});
emit('play-urls', urls);
}
function getSearch(q) {
if (q) {
const pq = q.split(' ').join('+');
history.pushState({}, '', `/search/${pq + getPipedQuery()}`);
document.title = 'Search Results for ' + q;
getResults(pq);
emit('lazy');
} else {
Reset();
history.pushState({}, '', '/');
document.title = 'Hyperpipe';
console.log('No Search');
}
}
async function getResults(q) {
const filters = ['music_songs', 'music_albums'];
for (let filter of filters) {
const json = await getJsonPiped(`/search?q=${q}&filter=${filter}`);
data[filter.split('_')[1]] = json;
console.log(json, data);
}
}
watch(
() => props.search,
(n) => {
if (n) {
n = n.replace(location.search || '', '');
console.log(n);
getSearch(n);
}
},
);
watch(
() => props.songItems,
(i) => {
console.log(i);
Reset();
data.songs = {}
data.songs.items = i.items;
data.albumTitle = i.title;
},
);
watch(
() => props.items,
(itms) => {
Reset();
for (let i in itms) {
data[i] = {}
data[i].items = itms[i];
console.log(data[i]);
}
},
);
</script> </script>
<template> <template>
<div v-if="songs && songs.corrected" class="text-full"> <div v-if="data.songs && data.songs.corrected" class="text-full">
I Fixed your Typo, "<span class="caps">{{ songs.suggestion }}</span I Fixed your Typo, "<span class="caps">{{ data.songs.suggestion }}</span
>"!! >"!!
</div> </div>
<div v-if="albumTitle" class="text-full flex"> <div v-if="data.albumTitle" class="text-full flex">
<PlayBtn @click="playAlbum" /> <PlayBtn @click="playAlbum" />
<span>{{ albumTitle }}</span> <span>{{ data.albumTitle }}</span>
</div> </div>
<div v-if="songs && songs.items[0]" class="search-songs"> <div v-if="data.songs && data.songs.items[0]" class="search-songs">
<h2>Top Songs</h2> <h2>Top Songs</h2>
<div class="grid"> <div class="grid">
<template v-for="song in songs.items"> <template v-for="song in data.songs.items">
<SongItem <SongItem
:author="song.uploaderName || ''" :author="song.uploaderName || ''"
:title="song.title || song.name" :title="song.title || song.name"
:channel="song.uploaderUrl || ''" :channel="song.uploaderUrl || ''"
:play="song.url || '/watch?v=' + song.videoId" :play="song.url || '/watch?v=' + song.id"
@open-song=" @open-song="
$emit('get-song', { $emit('play-urls', [{
url: song.url || '/watch?v=' + song.id, url: song.url || '/watch?v=' + song.id,
title: song.title || song.name, title: song.title || song.name,
}) }])
" "
@get-artist=" @get-artist="
(e) => { (e) => {
@ -56,17 +153,18 @@ defineEmits([
</template> </template>
</div> </div>
<a <a
v-if="notes" v-if="data.notes"
@click.prevent="$emit('get-album', '/playlist?list=' + notes.items)" @click.prevent="$emit('get-album', '/playlist?list=' + data.notes.items)"
class="more" class="more"
:href="'/playlist?list=' + data.notes.items"
>See All</a >See All</a
> >
</div> </div>
<div v-if="albums && albums.items[0]" class="search-albums"> <div v-if="data.albums && data.albums.items[0]" class="search-albums">
<h2>Albums</h2> <h2>Albums</h2>
<div class="grid-3"> <div class="grid-3">
<template v-for="album in albums.items"> <template v-for="album in data.albums.items">
<AlbumItem <AlbumItem
:author="album.uploaderName || album.subtitle" :author="album.uploaderName || album.subtitle"
:name="album.name || album.title" :name="album.name || album.title"
@ -81,11 +179,11 @@ defineEmits([
</div> </div>
<div <div
v-if="recommendedArtists && recommendedArtists.items[0]" v-if="data.recommendedArtists && data.recommendedArtists.items[0]"
class="search-artists"> class="search-artists">
<h2>Similar Artists</h2> <h2>Similar Artists</h2>
<div class="grid-3"> <div class="grid-3">
<template v-for="artist in recommendedArtists.items"> <template v-for="artist in data.recommendedArtists.items">
<AlbumItem <AlbumItem
:author="artist.subtitle" :author="artist.subtitle"
:name="artist.title" :name="artist.title"
@ -96,99 +194,6 @@ defineEmits([
</div> </div>
</template> </template>
<script>
import PlayBtn from './PlayBtn.vue';
import SongItem from './SongItem.vue';
import AlbumItem from './AlbumItem.vue';
export default {
components: {
PlayBtn,
SongItem,
AlbumItem,
},
data() {
return {
songs: null,
albums: null,
recommendedArtists: null,
albumTitle: null,
notes: null,
};
},
watch: {
search(NewSearch) {
console.log(NewSearch);
this.getSearch(NewSearch);
},
songItems(i) {
console.log(i);
this.Reset();
this.songs = {};
this.songs.items = i.items;
this.albumTitle = i.title;
},
items(itms) {
this.Reset();
for (let i in itms) {
this[i] = {};
this[i].items = itms[i];
console.log(this[i]);
}
},
},
methods: {
Reset() {
this.notes = null;
this.albums = null;
this.albumTitle = null;
this.songs = null;
this.recommendedArtists = null;
},
playAlbum() {
const urls = this.songs.items.map((item) => {
return { url: item.url, title: item.title };
});
this.$emit('play-urls', urls);
},
getSearch(q) {
if (q) {
history.pushState({}, '', `/search/${q}`);
document.title = 'Search Results for ' + q;
this.getResults(q.split(' ').join('+'));
this.$emit('lazy');
} else {
this.Reset();
history.pushState({}, '', '/');
document.title = 'Hyperpipe';
console.log('No Search');
}
},
getResults(q) {
const filters = ['music_songs', 'music_albums'];
for (let filter of filters) {
fetch(
`https://pipedapi.kavin.rocks/search?q=${q}&filter=${filter}`,
).then((res) => {
res.json().then((json) => {
this[filter.split('_')[1]] = json;
console.log(json);
});
});
}
},
},
};
</script>
<style scoped> <style scoped>
.search-albums, .search-albums,
.search-songs, .search-songs,
@ -196,11 +201,23 @@ export default {
place-items: start center; place-items: start center;
margin-bottom: 2rem; margin-bottom: 2rem;
} }
.search-songs h2,
.search-artists h2,
.search-albums h2 {
text-align: center;
}
.search-albums .grid-3, .search-albums .grid-3,
.search-artists .grid-3 { .search-artists .grid-3 {
display: grid; display: grid;
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.search-artists {
text-align: center;
}
.search-artists .bg-img {
border-radius: 50%;
margin-bottom: 0.5rem;
}
.text-full { .text-full {
padding: 1rem; padding: 1rem;
font-size: 1.5rem; font-size: 1.5rem;

View file

@ -1,11 +1,43 @@
<script setup> <script setup>
defineProps({ const props = defineProps({
author: String, author: String,
title: String, title: String,
channel: String, channel: String,
play: String, play: String,
}); });
defineEmits(['get-artist']);
const emit = defineEmits(['get-artist', 'open-song']);
function openSong(el) {
if (!el.classList.contains('ign')) {
emit('open-song', props.play);
}
}
async function Share() {
if ('share' in navigator) {
const data = {
title: `Listen to ${props.title} by ${props.author} on Hyperpipe`,
url: location.origin + props.play,
};
try {
await navigator.share(data);
console.log('Done Sharing!');
} catch (err) {
console.log(err);
}
} else {
navigator.clipboard.writeText(location.host + props.play).then(
() => {
console.log('Copied to Clipboard');
},
(err) => {
console.log(err);
},
);
}
}
</script> </script>
<template> <template>
<div class="song card flex pop" @click="openSong($event.target)"> <div class="song card flex pop" @click="openSong($event.target)">
@ -35,42 +67,6 @@ defineEmits(['get-artist']);
</div> </div>
</template> </template>
<script>
export default {
methods: {
openSong(el) {
if (!el.classList.contains('ign')) {
this.$emit('open-song', this.play);
}
},
async Share() {
if ('share' in navigator) {
const data = {
title: `Listen to ${this.title} by ${this.author} on Hyperpipe`,
url: location.origin + this.play,
};
try {
await navigator.share(data);
console.log('Done Sharing!');
} catch (err) {
console.log(err);
}
} else {
navigator.clipboard.writeText(location.host + this.play).then(
() => {
console.log('Copied to Clipboard');
},
(err) => {
console.log(err);
},
);
}
},
},
};
</script>
<style scoped> <style scoped>
.card { .card {
margin: 1rem 0; margin: 1rem 0;

View file

@ -1,13 +1,11 @@
<script setup> <script setup>
const props = defineProps({ defineProps({
state: String, state: String,
time: Number, time: Number,
show: Boolean, show: Boolean,
loop: Boolean, loop: Boolean,
}); });
defineEmits(['vol', 'play', 'list', 'loop']); defineEmits(['vol', 'play', 'list', 'loop', 'change-time']);
console.log(props);
</script> </script>
<template> <template>
<div id="statusbar" class="flex"> <div id="statusbar" class="flex">
@ -19,9 +17,14 @@ console.log(props);
<div <div
id="statusbar-progress" id="statusbar-progress"
class="progress" class="range-wrap"
:style="'--tw: ' + time + '%;'"> :style="'--fw:' + time + '%;'">
<div class="progress-thumb"></div> <input
:value="time"
type="range"
name="statusbar-progress"
max="100"
@input="$emit('change-time', $event.target.value)" />
</div> </div>
</div> </div>
@ -77,38 +80,6 @@ console.log(props);
.bi-infinity { .bi-infinity {
font-size: 1.75rem !important; font-size: 1.75rem !important;
} }
.progress {
--h: 0.25rem;
--w: 5rem;
--th: 100%;
--tw: 75%;
background: var(--color-border);
height: var(--h);
width: var(--w);
transition: width 0.4s ease;
}
.progress.v {
--th: 75%;
--tw: 100%;
--h: 5rem;
--w: 0.25rem;
transform: rotateZ(180deg);
z-index: 999999;
}
.progress-thumb {
width: var(--tw);
height: var(--th);
background: var(--color-foreground);
overflow: hidden;
transition: width 0.4s ease;
}
.progress-thumb:after {
content: '';
background-color: #fff;
height: 0.5rem;
width: 0.5rem;
border-radius: 50%;
}
.popup { .popup {
--h: 6.5rem; --h: 6.5rem;
--w: 1rem; --w: 1rem;
@ -125,6 +96,7 @@ input[type='range'] {
background: transparent; background: transparent;
color: transparent; color: transparent;
cursor: pointer; cursor: pointer;
position: relative;
} }
input[type='range']:focus { input[type='range']:focus {
outline: none; outline: none;
@ -132,16 +104,49 @@ input[type='range']:focus {
input[type='range']::-webkit-slider-thumb { input[type='range']::-webkit-slider-thumb {
-webkit-appearance: none; -webkit-appearance: none;
appearance: none; appearance: none;
opacity: 0;
transition: opacity 0.4s ease;
}
input[type='range']:hover::-webkit-slider-thumb,
#vol input[type='range']::-webkit-slider-thumb {
background-color: var(--color-foreground); background-color: var(--color-foreground);
opacity: 1;
height: 1rem; height: 1rem;
width: 1rem; width: 1rem;
border-radius: 50%; border-radius: 50%;
margin-top: -0.45rem; margin-top: -0.45rem;
} }
input[type='range']::-webkit-slider-runnable-track { input[type='range']::-webkit-slider-runnable-track {
height: 0.1rem; height: 100%;
background-color: var(--color-border); background-color: var(--color-border);
} }
#vol input[type='range']::-webkit-slider-runnable-track {
height: 0.1rem;
}
.range-wrap {
--fw: 0%;
display: flex;
align-items: center;
position: relative;
height: 0.25rem;
display: inline-block;
}
.range-wrap:before {
content: '';
width: var(--fw);
position: absolute;
left: 0;
bottom: 0;
height: 0.25rem;
background-color: var(--color-foreground);
transition: width 0.4s ease;
}
.range-wrap input[type='range'] {
--w: 100%;
--h: 100%;
position: absolute;
top: 0;
}
#statusbar-progress { #statusbar-progress {
width: 50vw; width: 50vw;
min-width: 200px; min-width: 200px;

34
src/scripts/fetch.js Normal file
View file

@ -0,0 +1,34 @@
export function getPipedQuery() {
const papi = new URLSearchParams(location.search).get('pipedapi')
if (!papi) {
return ''
}
return '?pipedapi=' + papi
}
export async function getJson(url) {
const res = await fetch(url).then((res) => res.json()).catch(err => { alert(err) });
if (!res.error) {
return res;
} else {
alert(
res.message
.replaceAll('Video', 'Audio')
.replaceAll('video', 'audio')
.replaceAll('watched', 'heard'),
);
console.error(res.message);
}
}
export async function getJsonPiped(path) {
const root =
new URLSearchParams(location.search).get('pipedapi') ||
localStorage.getItem('pipedapi') ||
'pipedapi.kavin.rocks';
return await getJson('https://' + root + path);
}