Skip to content

Commit b1fd0ac

Browse files
authored
Merge pull request #68 from mbaraa/dev
release v0.1.0
2 parents adc6e8a + 380564f commit b1fd0ac

File tree

15 files changed

+382
-87
lines changed

15 files changed

+382
-87
lines changed

app/handlers/apis/history.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@ import (
66
"dankmuzikk/handlers"
77
"dankmuzikk/log"
88
"dankmuzikk/services/history"
9+
"dankmuzikk/views/components/playlist"
910
"dankmuzikk/views/components/song"
1011
"fmt"
1112
"net/http"
1213
"strconv"
14+
15+
"github.com/a-h/templ"
1316
)
1417

1518
type historyApi struct {
@@ -43,8 +46,12 @@ func (h *historyApi) HandleGetMoreHistoryItems(w http.ResponseWriter, r *http.Re
4346
}
4447

4548
outBuf := bytes.NewBuffer([]byte{})
46-
for _, s := range recentPlays {
47-
song.Song(s, []string{"Played " + s.AddedAt}, nil, entities.Playlist{}).
49+
for idx, s := range recentPlays {
50+
song.Song(s, []string{"Played " + s.AddedAt},
51+
[]templ.Component{
52+
playlist.PlaylistsPopup((idx+1)*page, s.YtId),
53+
},
54+
entities.Playlist{}).
4855
Render(r.Context(), outBuf)
4956
}
5057

app/services/youtube/search/search_scraper.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ import (
1212
)
1313

1414
var (
15-
pat0 = regexp.MustCompile(`"innertubeApiKey":"([^"]*)`)
16-
pat = regexp.MustCompile(`ytInitialData[^{]*(.*?);\s*<\/script>`)
17-
pat2 = regexp.MustCompile(`ytInitialData"[^{]*(.*);\s*window\["ytInitialPlayerResponse"\]`)
15+
keyPattern = regexp.MustCompile(`"innertubeApiKey":"([^"]*)`)
16+
dataPattern = regexp.MustCompile(`ytInitialData[^{]*(.*?);\s*<\/script>`)
17+
dataPattern2 = regexp.MustCompile(`ytInitialData"[^{]*(.*);\s*window\["ytInitialPlayerResponse"\]`)
1818
)
1919

2020
type videoResult struct {
@@ -95,15 +95,15 @@ func search(q string) ([]videoResult, error) {
9595
Parser: "json_format",
9696
Key: "",
9797
}
98-
key := pat0.FindSubmatch(respBody)
98+
key := keyPattern.FindSubmatch(respBody)
9999
jojo.Key = string(key[1])
100100

101-
matches := pat.FindSubmatch(respBody)
101+
matches := dataPattern.FindSubmatch(respBody)
102102
if len(matches) > 1 {
103103
jojo.Parser += ".object_var"
104104
} else {
105105
jojo.Parser += ".original"
106-
matches = pat2.FindSubmatch(respBody)
106+
matches = dataPattern2.FindSubmatch(respBody)
107107
}
108108
data := ytSearchData{}
109109
err = json.Unmarshal(matches[1], &data)
@@ -134,7 +134,7 @@ func search(q string) ([]videoResult, error) {
134134
return resSuka, nil
135135
}
136136

137-
// ScraperSearch is a scrapper enabled YouTube search, using the search service under ~/ytscraper
137+
// ScraperSearch is a scrapper enabled YouTube search.
138138
type ScraperSearch struct{}
139139

140140
func (y *ScraperSearch) Search(query string) (results []entities.Song, err error) {

app/static/css/refresher.css

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/* I herby admit that this code is a copy-pasta from https://developer.chrome.com/blog/overscroll-behavior/ */
2+
3+
body.refreshing #main-contents,
4+
body.refreshing header {
5+
filter: blur(1px);
6+
touch-action: none;
7+
}
8+
9+
body.refreshing .refresher {
10+
transform: translate3d(0, 150%, 0) scale(1);
11+
z-index: 50;
12+
visibility: visible;
13+
}
14+
15+
.refresher {
16+
pointer-events: none;
17+
--refresh-width: 55px;
18+
background: var(--secondary-color);
19+
width: var(--refresh-width);
20+
height: var(--refresh-width);
21+
border-radius: 50%;
22+
position: absolute;
23+
left: calc(50% - var(--refresh-width) / 2);
24+
padding: 8px;
25+
box-shadow:
26+
0 2px 2px 0 rgba(0, 0, 0, 0.14),
27+
0 1px 5px 0 rgba(0, 0, 0, 0.12),
28+
0 3px 1px -2px rgba(0, 0, 0, 0.2);
29+
transition: all 0.5s cubic-bezier(0, 0, 0.2, 1);
30+
will-change: transform;
31+
display: inline-flex;
32+
justify-content: space-evenly;
33+
align-items: center;
34+
visibility: hidden;
35+
}
36+
37+
body.refreshing .refresher.shrink {
38+
transform: translate3d(0, 150%, 0) scale(0);
39+
opacity: 0;
40+
}
41+
42+
.refresher.done {
43+
transition: none;
44+
}
45+
46+
.loading-bar {
47+
background-color: var(--primary-color);
48+
width: 4px;
49+
height: 18px;
50+
border-radius: 4px;
51+
animation: loading 0.81s ease-in-out infinite;
52+
}
53+
54+
.loading-bar:nth-child(1) {
55+
animation-delay: 0;
56+
}
57+
.loading-bar:nth-child(2) {
58+
animation-delay: 0.09s;
59+
}
60+
.loading-bar:nth-child(3) {
61+
animation-delay: 0.18s;
62+
}
63+
.loading-bar:nth-child(4) {
64+
animation-delay: 0.27s;
65+
}
66+
67+
@keyframes loading {
68+
0% {
69+
transform: scale(1);
70+
}
71+
20% {
72+
transform: scale(1, 2.2);
73+
}
74+
40% {
75+
transform: scale(1);
76+
}
77+
}

app/static/js/player.js

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,8 @@ function playlister(state) {
376376
? 0
377377
: state.currentSongIdx + 1;
378378
const songToPlay = state.playlist.songs[state.currentSongIdx];
379-
playSongFromPlaylist(songToPlay.yt_id, state.playlist);
379+
await highlightSongInPlaylist(songToPlay.yt_id, state.playlist);
380+
await playSong(songToPlay);
380381
__setSongInPlaylistStyle(songToPlay.yt_id, state.playlist);
381382
};
382383

@@ -415,7 +416,8 @@ function playlister(state) {
415416
? state.playlist.songs.length - 1
416417
: state.currentSongIdx - 1;
417418
const songToPlay = state.playlist.songs[state.currentSongIdx];
418-
playSongFromPlaylist(songToPlay.yt_id, state.playlist);
419+
await highlightSongInPlaylist(songToPlay.yt_id, state.playlist);
420+
await playSong(songToPlay);
419421
__setSongInPlaylistStyle(songToPlay.yt_id, state.playlist);
420422
};
421423

@@ -645,6 +647,46 @@ async function playSingleSongNext(song) {
645647
alert(`Playing ${song.title} next!`);
646648
}
647649

650+
/**
651+
* @param {Playlist} playlist
652+
*/
653+
async function playPlaylistNext(playlist) {
654+
if (!playlist || !playlist.songs || playlist.songs.length === 0) {
655+
alert("Can't do that!");
656+
return;
657+
}
658+
if (playerState.playlist.songs.length === 0) {
659+
playSongFromPlaylist(playlist.songs[0].yt_id, playlist);
660+
return;
661+
}
662+
playerState.playlist.songs.splice(
663+
playerState.currentSongIdx + 1,
664+
0,
665+
...playlist.songs.map((s) => {
666+
return { ...s, votes: 1 };
667+
}),
668+
);
669+
playerState.playlist.title = `${playerState.playlist.title} + ${playlist.title}`;
670+
alert(`Playing ${playlist.title} next!`);
671+
}
672+
673+
/**
674+
* @param {Playlist} playlist
675+
*/
676+
async function appendPlaylistToCurrentQueue(playlist) {
677+
if (!playlist || !playlist.songs || playlist.songs.length === 0) {
678+
alert("Can't do that!");
679+
return;
680+
}
681+
if (playerState.playlist.songs.length === 0) {
682+
playSongFromPlaylist(playlist.songs[0].yt_id, playlist);
683+
return;
684+
}
685+
playerState.playlist.songs.push(...playlist.songs);
686+
playerState.playlist.title = `${playerState.playlist.title} + ${playlist.title}`;
687+
alert(`Playing ${playlist.title} next!`);
688+
}
689+
648690
/**
649691
* @param {string} songYtId
650692
* @param {Playlist} playlist
@@ -684,12 +726,6 @@ async function playSongFromPlaylist(songYtId, playlist) {
684726
* @param {Song} song
685727
*/
686728
function appendSongToCurrentQueue(song) {
687-
if (
688-
playerState.playlist.songs.findIndex((s) => s.yt_id === song.yt_id) !== -1
689-
) {
690-
alert(`${song.title} exists in the queue!`);
691-
return;
692-
}
693729
if (playerState.playlist.songs.length === 0) {
694730
playSingleSong(song);
695731
return;
@@ -917,8 +953,10 @@ window.Player.hidePlayer = hide;
917953
window.Player.playSingleSong = playSingleSong;
918954
window.Player.playSingleSongNext = playSingleSongNext;
919955
window.Player.playSongFromPlaylist = playSongFromPlaylist;
956+
window.Player.playPlaylistNext = playPlaylistNext;
920957
window.Player.removeSongFromPlaylist = removeSongFromPlaylist;
921958
window.Player.addSongToQueue = appendSongToCurrentQueue;
959+
window.Player.appendPlaylistToCurrentQueue = appendPlaylistToCurrentQueue;
922960
window.Player.stopMuzikk = stopMuzikk;
923961
window.Player.expand = () => expand();
924962
window.Player.collapse = () => collapse();

app/static/js/refresher.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/* I herby admit that this code is a copy-pasta from https://developer.chrome.com/blog/overscroll-behavior/ */
2+
3+
"use strict";
4+
5+
const mainContentsEl = document.getElementById("main-contents");
6+
let _startY = 0;
7+
8+
async function simulateRefreshAction() {
9+
const sleep = (timeout) =>
10+
new Promise((resolve) => setTimeout(resolve, timeout));
11+
12+
const transitionEnd = function (propertyName, node) {
13+
return new Promise((resolve) => {
14+
function callback(e) {
15+
e.stopPropagation();
16+
if (e.propertyName === propertyName) {
17+
node.removeEventListener("transitionend", callback);
18+
resolve(e);
19+
}
20+
}
21+
node.addEventListener("transitionend", callback);
22+
});
23+
};
24+
25+
const refresher = document.querySelector(".refresher");
26+
27+
document.body.classList.add("refreshing");
28+
await sleep(500);
29+
30+
refresher.classList.add("shrink");
31+
await transitionEnd("transform", refresher);
32+
refresher.classList.add("done");
33+
34+
refresher.classList.remove("shrink");
35+
document.body.classList.remove("refreshing");
36+
await sleep(0); // let new styles settle.
37+
refresher.classList.remove("done");
38+
}
39+
40+
document.body.addEventListener(
41+
"touchstart",
42+
(e) => {
43+
_startY = e.touches[0].pageY;
44+
},
45+
{ passive: true },
46+
);
47+
48+
document.body.addEventListener(
49+
"touchmove",
50+
async (e) => {
51+
const y = e.touches[0].pageY;
52+
if (
53+
document.scrollingElement.scrollTop === 0 &&
54+
y > _startY + 150 &&
55+
!document.body.classList.contains("refreshing")
56+
) {
57+
await simulateRefreshAction();
58+
await updateMainContent(window.location.pathname);
59+
}
60+
},
61+
{ passive: true },
62+
);

app/static/js/router.js

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,35 @@ window.addEventListener("load", () => {
2727
updateActiveNavLink();
2828
});
2929

30+
/**
31+
* @param {string} path the requested path to update.
32+
*/
33+
async function updateMainContent(path) {
34+
Utils.showLoading();
35+
await fetch(path + "?no_layout=true")
36+
.then((res) => res.text())
37+
.then((page) => {
38+
mainContentsEl.innerHTML = page;
39+
})
40+
.catch(() => {
41+
window.location.reload();
42+
})
43+
.finally(() => {
44+
Utils.hideLoading();
45+
updateActiveNavLink();
46+
});
47+
}
48+
49+
window.addEventListener("popstate", async (e) => {
50+
const mainContentsEl = document.getElementById("main-contents");
51+
if (!!mainContentsEl && !!e.target.location.pathname) {
52+
e.stopImmediatePropagation();
53+
e.preventDefault();
54+
await updateMainContent(e.target.location.pathname);
55+
return;
56+
}
57+
});
58+
3059
document.addEventListener("htmx:afterRequest", function (e) {
3160
if (!!e.detail && !!e.detail.xhr) {
3261
const newTitle = e.detail.xhr.getResponseHeader("HX-Title");
@@ -36,4 +65,4 @@ document.addEventListener("htmx:afterRequest", function (e) {
3665
}
3766
});
3867

39-
window.Router = { updateActiveNavLink };
68+
window.Router = { updateActiveNavLink, updateMainContent };

app/views/components/menus/mobile_menu.templ

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -39,31 +39,36 @@ templ MobileMenu(id, title string, button, child templ.Component) {
3939
.mobile-menu-collapsed {
4040
height: 0;
4141
max-height: 0;
42-
}
43-
.mobile-menu-exapnded {
44-
min-height: 200px;
45-
max-height: 500px;
42+
min-height: 0;
4643
}
4744
</style>
4845
}
4946

5047
script toggleMobileMenu(id string) {
5148
const popover = document.getElementById(`mobile-menu-${id}`);
52-
popover.classList.toggle("mobile-menu-exapnded");
49+
if (popover.classList.contains("mobile-menu-collapsed")) {
50+
const player = document.getElementById("ze-player");
51+
const rect = popover.getBoundingClientRect();
52+
popover.style.bottom = `-${
53+
(window.innerHeight-(rect.y+rect.height)) -
54+
65 -
55+
(!player.classList.contains("hidden")?
56+
player.getBoundingClientRect().height + 5:
57+
0)
58+
}px`
59+
popover.style.height = (
60+
popover.children[0].getBoundingClientRect().height + 4 +
61+
popover.children[1].getBoundingClientRect().height
62+
).toString() + "px" ;
63+
popover.style.maxHeight = "500px";
64+
popover.style.minHeight = "200px";
5365

54-
const player = document.getElementById("ze-player");
55-
console.log(player.getBoundingClientRect())
66+
popover.classList.remove("mobile-menu-collapsed");
67+
} else {
68+
popover.classList.add("mobile-menu-collapsed");
69+
popover.style.height = 0;
70+
popover.style.maxHeight = 0;
71+
popover.style.minHeight = 0;
72+
}
5673

57-
const rect = popover.getBoundingClientRect();
58-
popover.style.bottom = `-${
59-
(window.innerHeight-(rect.y+rect.height)) -
60-
65 -
61-
(!player.classList.contains("hidden")?
62-
player.getBoundingClientRect().height + 5:
63-
0)
64-
}px`
65-
popover.style.height = (
66-
popover.children[0].getBoundingClientRect().height + 4 +
67-
popover.children[1].getBoundingClientRect().height
68-
).toString() + "px" ;
6974
}

0 commit comments

Comments
 (0)