Add context menu for audios

This commit is contained in:
mrilyew 2024-11-30 15:35:43 +03:00
parent 2cff603e11
commit 1d719309c7
15 changed files with 270 additions and 25 deletions

View file

@ -97,9 +97,12 @@ final class AudioPresenter extends OpenVKPresenter
$this->template->club = $owner < 0 ? $entity : NULL; $this->template->club = $owner < 0 ? $entity : NULL;
$this->template->isMy = ($owner > 0 && ($entity->getId() === $this->user->id)); $this->template->isMy = ($owner > 0 && ($entity->getId() === $this->user->id));
$this->template->isMyClub = ($owner < 0 && $entity->canBeModifiedBy($this->user->identity)); $this->template->isMyClub = ($owner < 0 && $entity->canBeModifiedBy($this->user->identity));
} else { } else if ($mode === 'alone_audio') {
$audios = $this->audios->getPopular(); $audios = [$this->template->alone_audio];
$audiosCount = $audios->size(); $audiosCount = 1;
$this->template->owner = $this->user->identity;
$this->template->ownerId = $this->user->id;
} }
// $this->renderApp("owner=$owner"); // $this->renderApp("owner=$owner");
@ -271,6 +274,19 @@ final class AudioPresenter extends OpenVKPresenter
} }
} }
function renderAloneAudio(int $owner_id, int $audio_id): void
{
$this->assertUserLoggedIn();
$found_audio = $this->audios->get($audio_id);
if(!$found_audio || $found_audio->isDeleted() || !$found_audio->canBeViewedBy($this->user->identity)) {
$this->notFound();
}
$this->template->alone_audio = $found_audio;
$this->renderList(NULL, 'alone_audio');
}
function renderListen(int $id): void function renderListen(int $id): void
{ {
if ($_SERVER["REQUEST_METHOD"] === "POST") { if ($_SERVER["REQUEST_METHOD"] === "POST") {
@ -762,6 +778,15 @@ final class AudioPresenter extends OpenVKPresenter
$audios = $stream->page($page, 10); $audios = $stream->page($page, 10);
$audiosCount = $stream->size(); $audiosCount = $stream->size();
break; break;
case 'alone_audio':
$found_audio = $this->audios->get($ctx_id);
if(!$found_audio || $found_audio->isDeleted() || !$found_audio->canBeViewedBy($this->user->identity)) {
$this->flashFail("err", "Error", "Not found", 89, true);
}
$audios = [$found_audio];
$audiosCount = 1;
break;
} }
$pagesCount = ceil($audiosCount / $perPage); $pagesCount = ceil($audiosCount / $perPage);

View file

@ -11,6 +11,8 @@
{_audio_new} {_audio_new}
{elseif $mode == 'popular'} {elseif $mode == 'popular'}
{_audio_popular} {_audio_popular}
{elseif $mode == 'alone_audio'}
{$alone_audio->getName()}
{else} {else}
{if $ownerId > 0} {if $ownerId > 0}
{_playlists} {$owner->getMorphedName("genitive", false)} {_playlists} {$owner->getMorphedName("genitive", false)}
@ -47,6 +49,10 @@
» »
{if $isMy}{_my_playlists}{else}{_playlists}{/if} {if $isMy}{_my_playlists}{else}{_playlists}{/if}
</div> </div>
<div n:if="$mode == 'alone_audio'">
{_my_audios_small}
</div>
{/block} {/block}
{block content} {block content}
@ -62,6 +68,12 @@
entity_id: {$ownerId}, entity_id: {$ownerId},
page: {$page} page: {$page}
} }
{elseif $mode == 'alone_audio'}
window.__current_page_audio_context = {
name: 'alone_audio',
entity_id: {$alone_audio->getId()},
page: 1
}
{/if} {/if}
</script> </script>

View file

@ -1,4 +1,4 @@
<div n:class="bigPlayer, $tidy ? tidy"> <div n:class="bigPlayer, ctx_place, $tidy ? tidy">
<div class="bigPlayerWrapper"> <div class="bigPlayerWrapper">
<div class="playButtons"> <div class="playButtons">
<div class="playButton musicIcon" data-tip='simple' data-title="{_play_tip} [Space]"></div> <div class="playButton musicIcon" data-tip='simple' data-title="{_play_tip} [Space]"></div>

View file

@ -3,7 +3,7 @@
{php $isAvailable = $audio->isAvailable()} {php $isAvailable = $audio->isAvailable()}
{php $performers = $audio->getPerformers()} {php $performers = $audio->getPerformers()}
{php $editable = isset($thisUser) && $audio->canBeModifiedBy($thisUser)} {php $editable = isset($thisUser) && $audio->canBeModifiedBy($thisUser)}
<div id="audioEmbed-{$id}" data-realid="{$audio->getId()}" {if $hideButtons}data-prettyid="{$audio->getPrettyId()}"{/if} data-name="{$audio->getName()}" data-genre="{$audio->getGenre()}" n:class="audioEmbed, !$isAvailable ? processed, $isWithdrawn ? withdrawn" data-length="{$audio->getLength()}" data-keys="{json_encode($audio->getKeys())}" data-url="{$audio->getURL()}"> <div id="audioEmbed-{$id}" data-realid="{$audio->getId()}" {if $hideButtons}data-prettyid="{$audio->getPrettyId()}"{/if} data-name="{$audio->getName()}" data-genre="{$audio->getGenre()}" n:class="audioEmbed, ctx_place, !$isAvailable ? processed, $isWithdrawn ? withdrawn" data-length="{$audio->getLength()}" data-keys="{json_encode($audio->getKeys())}" data-url="{$audio->getURL()}">
<audio class="audio" /> <audio class="audio" />
<div id="miniplayer" class="audioEntry"> <div id="miniplayer" class="audioEntry">

View file

@ -203,6 +203,8 @@ routes:
handler: "Audio->list" handler: "Audio->list"
- url: "/audio{num}/listen" - url: "/audio{num}/listen"
handler: "Audio->listen" handler: "Audio->listen"
- url: "/audio{num}_{num}"
handler: "Audio->aloneAudio"
- url: "/audios/search" - url: "/audios/search"
handler: "Audio->search" handler: "Audio->search"
- url: "/audios/newPlaylist" - url: "/audios/newPlaylist"

View file

@ -34,7 +34,7 @@
box-shadow: 1px 0px 8px 0px rgba(34, 60, 80, 0.2); box-shadow: 1px 0px 8px 0px rgba(34, 60, 80, 0.2);
position: sticky; position: sticky;
top: 0px; top: 0px;
z-index: 1; z-index: 10;
} }
/* for search */ /* for search */

View file

@ -139,7 +139,7 @@ body.dimmed > .dimmer #absolute_territory {
} }
.miniplayer { .miniplayer {
position: absolute; position: fixed;
background: rgba(54, 54, 54, 0.95); background: rgba(54, 54, 54, 0.95);
border-radius: 3px; border-radius: 3px;
min-width: 299px; min-width: 299px;

View file

@ -3859,3 +3859,36 @@ hr {
.tippy-box[data-animation='up_down'][data-state='visible'] { .tippy-box[data-animation='up_down'][data-state='visible'] {
inset: auto auto 0px 0px; inset: auto auto 0px 0px;
} }
.ctx_place {
position: relative;
}
#ctx_menu {
position: absolute;
z-index: 100;
background: #f7f7f7;
border: 1px solid #d8d8d8;
display: flex;
flex-direction: column;
min-width: 185px;
}
#ctx_menu a {
padding: 6px 6px 6px 26px;
color: black;
position: relative;
}
#ctx_menu a:hover {
background: #e9e9e9;
}
#ctx_menu a.pressed::before {
content: '✓';
display: inline-block;
position: absolute;
left: 9px;
top: 3px;
font-size: 15px;
}

View file

@ -217,7 +217,7 @@ window.player = new class {
form_data.append('context_entity', this.context.object.entity_id) form_data.append('context_entity', this.context.object.entity_id)
break break
case 'classic_search_context': case 'classic_search_context':
// todo rifictir // tidi riwriti
form_data.append('context', this.context.object.name) form_data.append('context', this.context.object.name)
form_data.append('context_entity', JSON.stringify({ form_data.append('context_entity', JSON.stringify({
'order': this.context.object.order, 'order': this.context.object.order,
@ -228,6 +228,9 @@ window.player = new class {
'query': this.context.object.query, 'query': this.context.object.query,
})) }))
break break
case 'alone_audio':
form_data.append('context', this.context.object.name)
form_data.append('context_entity', this.context.object.entity_id)
} }
form_data.append('page', page) form_data.append('page', page)
@ -395,6 +398,7 @@ window.player = new class {
async shuffle() { async shuffle() {
this.tracks.sort(() => Math.random() - 0.59) this.tracks.sort(() => Math.random() - 0.59)
await this.setTrack(this.tracks.at(0).id) await this.setTrack(this.tracks.at(0).id)
this.play()
} }
isAtAudiosPage() { isAtAudiosPage() {
@ -447,7 +451,7 @@ window.player = new class {
this.__updateFace() this.__updateFace()
} }
} else { } else {
this.ajClose() this.ajClose(false)
this.is_closed = false this.is_closed = false
if(this.tracks.length < 1) { if(this.tracks.length < 1) {
if(window.__current_page_audio_context) { if(window.__current_page_audio_context) {
@ -622,9 +626,12 @@ window.player = new class {
} }
} }
ajClose() { ajClose(pause = true) {
this.is_closed = true this.is_closed = true
this.pause() if(pause) {
this.pause()
}
u('#ajax_audio_player').addClass('hidden') u('#ajax_audio_player').addClass('hidden')
} }
@ -637,7 +644,7 @@ window.player = new class {
const previous_time_x = localStorage.getItem('audio.lastX') ?? 100 const previous_time_x = localStorage.getItem('audio.lastX') ?? 100
const previous_time_y = localStorage.getItem('audio.lastY') ?? scrollY const previous_time_y = localStorage.getItem('audio.lastY') ?? scrollY
const miniplayer_template = u(` const miniplayer_template = u(`
<div id='ajax_audio_player'> <div id='ajax_audio_player' class='ctx_place'>
<div id='aj_player'> <div id='aj_player'>
<div id='aj_player_internal_controls'> <div id='aj_player_internal_controls'>
<div id='aj_player_play'> <div id='aj_player_play'>
@ -691,7 +698,7 @@ window.player = new class {
$('#ajax_audio_player').draggable({ $('#ajax_audio_player').draggable({
cursor: 'grabbing', cursor: 'grabbing',
containment: 'window', containment: 'window',
cancel: '#aj_player_track .selectableTrack, #aj_player_volume .selectableTrack', cancel: '#aj_player_track, #aj_player_volume, #aj_player_buttons',
stop: function(e) { stop: function(e) {
if(window.player.ajaxPlayer.length > 0) { if(window.player.ajaxPlayer.length > 0) {
const left = parseInt(window.player.ajaxPlayer.nodes[0].style.left) const left = parseInt(window.player.ajaxPlayer.nodes[0].style.left)
@ -1022,6 +1029,127 @@ u(document).on("drop", '.audiosContainer', function(e) {
} }
}) })
u(document).on('contextmenu', '.bigPlayer, .audioEmbed, #ajax_audio_player', (e) => {
e.preventDefault()
u('#ctx_menu').remove()
const ctx_type = u(e.target).closest('.bigPlayer, #ajax_audio_player').length > 0 ? 'main_player' : 'mini_player'
const parent = e.target.closest('.ctx_place')
if(!parent) {
return
}
const rect = parent.getBoundingClientRect()
let x, y;
let rx = rect.x + window.scrollX, ry = rect.y + window.scrollY
x = e.pageX - rx
y = e.pageY - ry
const ctx_u = u(`
<div id='ctx_menu' style='top:${y}px;left:${x}px;' data-type='ctx_type'>
<a id='audio_ctx_copy'>${tr('copy_link_to_audio')}</a>
${ctx_type == 'main_player' ? `
<a id='audio_ctx_repeat' ${window.player.audioPlayer.loop ? `class='pressed'` : ''}>${tr('repeat_tip')}</a>
<a id='audio_ctx_shuffle'>${tr('shuffle_tip')}</a>
<a id='audio_ctx_mute' ${window.player.audioPlayer.muted ? `class='pressed'` : ''}>${tr('mute_tip_noun')}</a>
` : ''}
${ctx_type == 'mini_player' ? `
<a id='audio_ctx_play_next'>${tr('audio_ctx_play_next')}</a>
` : ''}
<a id='audio_ctx_add_to_group'>${tr('audio_ctx_add_to_group')}</a>
<a id='audio_ctx_add_to_playlist'>${tr('audio_ctx_add_to_playlist')}</a>
${ctx_type == 'main_player' ? `<a href='https://github.com/mrilyew' target='_blank'>BigPlayer v1.1 by MrIlyew</a>` : ''}
</div>
`)
u(parent).append(ctx_u)
ctx_u.find('#audio_ctx_copy').on('click', async (e) => {
if(ctx_type == 'main_player') {
if(window.player.current_track_id == 0) {
makeError(tr('copy_link_to_audio_error_not_selected_track'), 'Red', 4000, 80)
return
}
const url = location.origin + `/audio${window.openvk.current_id}_${window.player.current_track_id}`
await copyToClipboard(url)
} else {
const url = location.origin + `/audio${window.openvk.current_id}_${u(e.target).closest('.audioEmbed').attr('data-realid')}`
await copyToClipboard(url)
}
})
ctx_u.find('#audio_ctx_repeat').on('click', () => {
if(window.player.current_track_id == 0) {
return
}
if(!window.player.audioPlayer.loop) {
window.player.audioPlayer.loop = true
window.player.uiPlayer.find('.repeatButton').addClass('pressed')
} else {
window.player.audioPlayer.loop = false
window.player.uiPlayer.find('.repeatButton').removeClass('pressed')
}
})
ctx_u.find('#audio_ctx_shuffle').on('click', async () => {
if(window.player.current_track_id == 0) {
return
}
await window.player.shuffle()
})
ctx_u.find('#audio_ctx_mute').on('click', async () => {
if(window.player.current_track_id == 0) {
return
}
window.player.uiPlayer.find('.deviceButton').toggleClass('pressed')
window.player.audioPlayer.muted = window.player.uiPlayer.find('.deviceButton').hasClass('pressed')
})
ctx_u.find('#audio_ctx_add_to_group').on('click', async () => {
if(ctx_type == 'main_player') {
if(window.player.current_track_id == 0) {
return
}
__showAudioAddDialog(window.player.current_track_id)
} else {
__showAudioAddDialog(Number(u(e.target).closest('.audioEmbed').attr('data-realid')))
}
})
ctx_u.find('#audio_ctx_add_to_playlist').on('click', async () => {
if(ctx_type == 'main_player') {
if(window.player.current_track_id == 0) {
return
}
__showAudioAddDialog(window.player.current_track_id, 'playlist')
} else {
__showAudioAddDialog(Number(u(e.target).closest('.audioEmbed').attr('data-realid')), 'playlist')
}
})
ctx_u.find('#audio_ctx_play_next').on('click', (ev) => {
const current_id = window.player.current_track_id
const move_id = Number(u(e.target).closest('.audioEmbed').attr('data-realid'))
if(current_id == 0) {
return
}
if(current_id == move_id) {
return
}
const current_index = window.player.__findTrack(current_id, true)
const next_track = window.player.__findTrack(move_id)
const next_track_player = u(`.audioEmbed[data-realid='${window.player.nextTrack.id}']`)
const moving_track_player = u(`.audioEmbed[data-realid='${move_id}']`)
window.player.tracks.splice(current_index + 1, 0, next_track)
if(next_track_player.length > 0 && moving_track_player.length > 0) {
next_track_player.nodes[0].outerHTML = moving_track_player.nodes[0].outerHTML + next_track_player.nodes[0].outerHTML
moving_track_player.remove()
}
})
})
u(document).on("click", ".musicIcon.edit-icon", (e) => { u(document).on("click", ".musicIcon.edit-icon", (e) => {
const player = e.target.closest(".audioEmbed") const player = e.target.closest(".audioEmbed")
const id = Number(player.dataset.realid) const id = Number(player.dataset.realid)
@ -1241,9 +1369,7 @@ $(document).on("click", ".musicIcon.remove-icon-group", (e) => {
}) })
}) })
$(document).on("click", ".musicIcon.add-icon-group", async (ev) => { function __showAudioAddDialog(id, current_tab = 'club') {
let current_tab = 'club';
const id = Number(ev.target.dataset.id)
const body = ` const body = `
<div id='_addAudioAdditional'> <div id='_addAudioAdditional'>
<div id='_tabs'> <div id='_tabs'>
@ -1380,12 +1506,12 @@ $(document).on("click", ".musicIcon.add-icon-group", async (ev) => {
<a href='/playlist${el.owner_id}_${el.id}' class='avatar'> <a href='/playlist${el.owner_id}_${el.id}' class='avatar'>
<img src='${el.cover_url}' alt='cover'> <img src='${el.cover_url}' alt='cover'>
</a> </a>
<div class='info'> <div class='info'>
<b class='noOverflow' value="${el.owner_id}_${el.id}">${ovk_proc_strtr(escapeHtml(el.title), 100)}</b> <b class='noOverflow' value="${el.owner_id}_${el.id}">${ovk_proc_strtr(escapeHtml(el.title), 100)}</b>
</div> </div>
</div> </div>
<div class='third_column'> <div class='third_column'>
<input type='checkbox' name='add_to'> <input type='checkbox' name='add_to'>
</div> </div>
@ -1437,12 +1563,16 @@ $(document).on("click", ".musicIcon.add-icon-group", async (ev) => {
u("#_addAudioAdditional").on("click", ".mb_tab a", async (e) => { u("#_addAudioAdditional").on("click", ".mb_tab a", async (e) => {
await switchTab(u(e.target).closest('.mb_tab').attr('data-name')) await switchTab(u(e.target).closest('.mb_tab').attr('data-name'))
}) })
u("#_addAudioAdditional").on("click", "input[name='add_to']", async (e) => { u("#_addAudioAdditional").on("click", "input[name='add_to']", async (e) => {
if(u(`input[name='add_to']:checked`).length > 10) { if(u(`input[name='add_to']:checked`).length > 10) {
e.preventDefault() e.preventDefault()
} }
}) })
}
$(document).on("click", ".musicIcon.add-icon-group", async (ev) => {
const id = Number(ev.target.dataset.id)
__showAudioAddDialog(id)
}) })
$(document).on("click", ".musicIcon.add-icon", (e) => { $(document).on("click", ".musicIcon.add-icon", (e) => {

View file

@ -436,7 +436,7 @@ async function OpenVideo(video_arr = [], init_player = true)
u('.miniplayer').remove() u('.miniplayer').remove()
}) })
$('.miniplayer').draggable({cursor: 'grabbing', containment: 'body', cancel: '.miniplayer-body'}) $('.miniplayer').draggable({cursor: 'grabbing', containment: 'window', cancel: '.miniplayer-body'})
$('.miniplayer').resizable({ $('.miniplayer').resizable({
maxHeight: 2000, maxHeight: 2000,
maxWidth: 3000, maxWidth: 3000,
@ -2405,14 +2405,15 @@ function setStatusEditorShown(shown) {
document.getElementById("status_editor").style.display = shown ? "block" : "none"; document.getElementById("status_editor").style.display = shown ? "block" : "none";
} }
document.addEventListener("click", event => { u(document).on('click', (event) => {
u('#ctx_menu').remove()
if(u('#status_editor').length < 1) { if(u('#status_editor').length < 1) {
return return
} }
if(!event.target.closest("#status_editor") && !event.target.closest("#page_status_text")) if(!event.target.closest("#status_editor") && !event.target.closest("#page_status_text"))
setStatusEditorShown(false); setStatusEditorShown(false);
}); })
u(document).on('click', '#page_status_text', (e) => { u(document).on('click', '#page_status_text', (e) => {
setStatusEditorShown(true) setStatusEditorShown(true)

View file

@ -102,12 +102,14 @@ class CMessageBox {
u('body').removeClass('dimmed') u('body').removeClass('dimmed')
u('html').attr('style', 'overflow-y:scroll') u('html').attr('style', 'overflow-y:scroll')
this.getNode().attr('style', 'display: none;') this.getNode().attr('style', 'display: none;')
this.hidden = true
} }
reveal() { reveal() {
u('body').addClass('dimmed') u('body').addClass('dimmed')
u('html').attr('style', 'overflow-y:hidden') u('html').attr('style', 'overflow-y:hidden')
this.getNode().attr('style', 'display: block;') this.getNode().attr('style', 'display: block;')
this.hidden = false
} }
static toggleLoader() { static toggleLoader() {

View file

@ -48,7 +48,13 @@ window.router = new class {
} }
__closeMsgs() { __closeMsgs() {
window.messagebox_stack.forEach(msg => msg.close()) window.messagebox_stack.forEach(msg => {
if(msg.hidden) {
return
}
msg.close()
})
} }
__appendPage(parsed_content) { __appendPage(parsed_content) {
@ -218,6 +224,11 @@ u(document).on('click', 'a', async (e) => {
return return
} }
if(target.download != null) {
console.log('AJAX | Skipped because its download')
return
}
if(!dom_url || dom_url == '#' || dom_url.indexOf('javascript:') != -1) { if(!dom_url || dom_url == '#' || dom_url.indexOf('javascript:') != -1) {
console.log('AJAX | Skipped because its anchor or function call') console.log('AJAX | Skipped because its anchor or function call')
return return
@ -307,8 +318,7 @@ u(document).on('submit', 'form', async (e) => {
history.pushState({'from_router': 1}, '', __new_url) history.pushState({'from_router': 1}, '', __new_url)
} }
console.log(form_res)
window.router.__appendPage(parsed_content) window.router.__appendPage(parsed_content)
await window.router.__integratePage() await window.router.__integratePage()

View file

@ -222,3 +222,19 @@ function serializeForm(form)
return fd return fd
} }
async function copyToClipboard(text) {
let fallback = () => {
prompt(text);
}
if(typeof navigator.clipboard == "undefined") {
fallback()
} else {
try {
await navigator.clipboard.writeText(text)
} catch(e) {
fallback()
}
}
}

View file

@ -995,9 +995,16 @@
"repeat_tip" = "Repeat"; "repeat_tip" = "Repeat";
"shuffle_tip" = "Shuffle"; "shuffle_tip" = "Shuffle";
"mute_tip" = "Mute"; "mute_tip" = "Mute";
"mute_tip_noun" = "Muted";
"playlist_hide_from_search" = "Unlisted"; "playlist_hide_from_search" = "Unlisted";
"confirm_deleting_audio" = "Do you sure want to delete this audio?"; "confirm_deleting_audio" = "Do you sure want to delete this audio?";
"copy_link_to_audio" = "Copy link to clipboard";
"copy_link_to_audio_error_not_selected_track" = "Track was not selected";
"audio_ctx_add_to_group" = "Add to group";
"audio_ctx_add_to_playlist" = "Add to playlist";
"audio_ctx_play_next" = "Play next";
/* Notifications */ /* Notifications */
"feedback" = "Feedback"; "feedback" = "Feedback";

View file

@ -950,9 +950,16 @@
"repeat_tip" = "Повторение"; "repeat_tip" = "Повторение";
"shuffle_tip" = "Перемешать"; "shuffle_tip" = "Перемешать";
"mute_tip" = "Заглушить"; "mute_tip" = "Заглушить";
"mute_tip_noun" = "Заглушено";
"playlist_hide_from_search" = "Не показывать в поиске"; "playlist_hide_from_search" = "Не показывать в поиске";
"confirm_deleting_audio" = "Вы действительно хотите полностью удалить аудиозапись?"; "confirm_deleting_audio" = "Вы действительно хотите полностью удалить аудиозапись?";
"copy_link_to_audio" = "Копировать ссылку на аудио";
"copy_link_to_audio_error_not_selected_track" = "Трек не выбран";
"audio_ctx_add_to_group" = "Добавить в группу";
"audio_ctx_add_to_playlist" = "Добавить в плейлист";
"audio_ctx_play_next" = "Воспроизвести следующим";
/* Notifications */ /* Notifications */
"feedback" = "Ответы"; "feedback" = "Ответы";