mirror of
https://github.com/openvk/openvk
synced 2025-04-23 00:23:01 +03:00
Interface updade
This commit is contained in:
parent
de0155b31c
commit
6966ec0b89
32 changed files with 1170 additions and 781 deletions
|
@ -15,6 +15,7 @@ use openvk\Web\Models\Entities\Video;
|
|||
use openvk\Web\Models\Repositories\Videos as VideosRepo;
|
||||
use openvk\Web\Models\Entities\Note;
|
||||
use openvk\Web\Models\Repositories\Notes as NotesRepo;
|
||||
use openvk\Web\Models\Repositories\Audios as AudiosRepo;
|
||||
|
||||
final class Wall extends VKAPIRequestHandler
|
||||
{
|
||||
|
@ -450,6 +451,8 @@ final class Wall extends VKAPIRequestHandler
|
|||
$attachmentType = "video";
|
||||
elseif(str_contains($attac, "note"))
|
||||
$attachmentType = "note";
|
||||
elseif(str_contains($attac, "audio"))
|
||||
$attachmentType = "audio";
|
||||
else
|
||||
$this->fail(205, "Unknown attachment type");
|
||||
|
||||
|
@ -483,6 +486,12 @@ final class Wall extends VKAPIRequestHandler
|
|||
if(!$attacc->getOwner()->getPrivacyPermission('notes.read', $this->getUser()))
|
||||
$this->fail(11, "Access to note denied");
|
||||
|
||||
$post->attach($attacc);
|
||||
} elseif($attachmentType == "audio") {
|
||||
$attacc = (new AudiosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId);
|
||||
if(!$attacc || $attacc->isDeleted())
|
||||
$this->fail(100, "Audio does not exist");
|
||||
|
||||
$post->attach($attacc);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -101,7 +101,7 @@ class Audio extends Media
|
|||
$this->stateChanges("segment_size", $ss);
|
||||
$this->stateChanges("length", $duration);
|
||||
|
||||
try {
|
||||
try {
|
||||
$args = [
|
||||
str_replace("enabled", "available", OPENVK_ROOT),
|
||||
str_replace("enabled", "available", $this->getBaseDir()),
|
||||
|
@ -114,23 +114,18 @@ class Audio extends Media
|
|||
$ss,
|
||||
];
|
||||
|
||||
|
||||
if(Shell::isPowershell()) {
|
||||
Shell::powershell("-executionpolicy bypass", "-File", __DIR__ . "/../shell/processAudio.ps1", ...$args)
|
||||
->start();
|
||||
->start();
|
||||
} else {
|
||||
Shell::bash(__DIR__ . "/../shell/processAudio.sh", ...$args)->start();
|
||||
// Shell::bash(__DIR__ . "/../shell/processAudio.sh", ...$args)->start();
|
||||
// exit("pwsh /opt/chandler/extensions/available/openvk/Web/Models/shell/processAudio.ps1 " . implode(" ", $args) . ' *> /opt/chandler/extensions/available/openvk/storage/log.log');
|
||||
// exit("pwsh /opt/chandler/extensions/available/openvk/Web/Models/shell/processAudio.ps1 " . implode(" ", $args) . ' *> /opt/chandler/extensions/available/openvk/storage/log.log');
|
||||
// Shell::bash("pwsh /opt/chandler/extensions/available/openvk/Web/Models/shell/processAudio.ps1 " . implode(" ", $args) . ' *> /opt/chandler/extensions/available/openvk/storage/log.log');
|
||||
exit("Linux uploads are not implemented");
|
||||
}
|
||||
|
||||
# Wait until processAudio will consume the file
|
||||
// $start = time();
|
||||
// while(file_exists($filename))
|
||||
// if(time() - $start > 5)
|
||||
// exit("Timed out waiting for ffmpeg"); // TODO replace with exception
|
||||
$start = time();
|
||||
while(file_exists($filename))
|
||||
if(time() - $start > 5)
|
||||
exit("Timed out waiting for ffmpeg"); // TODO replace with exception
|
||||
|
||||
} catch(UnknownCommandException $ucex) {
|
||||
exit(OPENVK_ROOT_CONF["openvk"]["debug"] ? "bash/pwsh is not installed" : VIDEOS_FRIENDLY_ERROR);
|
||||
|
@ -161,7 +156,7 @@ class Audio extends Media
|
|||
|
||||
function getLyrics(): ?string
|
||||
{
|
||||
return $this->getRecord()->lyrics ?? NULL;
|
||||
return !is_null($this->getRecord()->lyrics) ? htmlspecialchars($this->getRecord()->lyrics, ENT_DISALLOWED | ENT_XHTML) : NULL;
|
||||
}
|
||||
|
||||
function getLength(): int
|
||||
|
|
|
@ -195,12 +195,12 @@ class Audios
|
|||
|
||||
function getNew(): EntityStream
|
||||
{
|
||||
return new EntityStream("Audio", $this->audios->where("created >= " . (time() - 259200))->order("created DESC")->limit(25));
|
||||
return new EntityStream("Audio", $this->audios->where("created >= " . (time() - 259200))->where(["withdrawn" => 0, "deleted" => 0, "unlisted" => 0])->order("created DESC")->limit(25));
|
||||
}
|
||||
|
||||
function getPopular(): EntityStream
|
||||
{
|
||||
return new EntityStream("Audio", $this->audios->where("listens > 0")->order("listens DESC")->limit(25));
|
||||
return new EntityStream("Audio", $this->audios->where("listens > 0")->where(["withdrawn" => 0, "deleted" => 0, "unlisted" => 0])->order("listens DESC")->limit(25));
|
||||
}
|
||||
|
||||
function isAdded(int $user_id, int $audio_id): bool
|
||||
|
@ -211,12 +211,47 @@ class Audios
|
|||
])->fetch());
|
||||
}
|
||||
|
||||
function find(string $query, int $page = 1, ?int $perPage = NULL): \Traversable
|
||||
function find(string $query, array $pars = [], string $sort = "id DESC", int $page = 1, ?int $perPage = NULL): \Traversable
|
||||
{
|
||||
$query = "%$query%";
|
||||
$result = $this->audios->where("name LIKE ? OR performer LIKE ?", $query, $query);
|
||||
$result = $this->audios->where([
|
||||
"unlisted" => 0,
|
||||
"deleted" => 0,
|
||||
]);
|
||||
|
||||
return new Util\EntityStream("Audio", $result);
|
||||
$notNullParams = [];
|
||||
|
||||
foreach($pars as $paramName => $paramValue)
|
||||
if($paramName != "before" && $paramName != "after" && $paramName != "only_performers")
|
||||
$paramValue != NULL ? $notNullParams+=["$paramName" => "%$paramValue%"] : NULL;
|
||||
else
|
||||
$paramValue != NULL ? $notNullParams+=["$paramName" => "$paramValue"] : NULL;
|
||||
|
||||
$nnparamsCount = sizeof($notNullParams);
|
||||
|
||||
if($notNullParams["only_performers"] == "1") {
|
||||
$result->where("performer LIKE ?", $query);
|
||||
} else {
|
||||
$result->where("name LIKE ? OR performer LIKE ?", $query, $query);
|
||||
}
|
||||
|
||||
if($nnparamsCount > 0) {
|
||||
foreach($notNullParams as $paramName => $paramValue) {
|
||||
switch($paramName) {
|
||||
case "before":
|
||||
$result->where("created < ?", $paramValue);
|
||||
break;
|
||||
case "after":
|
||||
$result->where("created > ?", $paramValue);
|
||||
break;
|
||||
case "with_lyrics":
|
||||
$result->where("lyrics IS NOT NULL");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Util\EntityStream("Audio", $result->order($sort));
|
||||
}
|
||||
|
||||
function findPlaylists(string $query, int $page = 1, ?int $perPage = NULL): \Traversable
|
||||
|
|
|
@ -4,18 +4,18 @@ use Chandler\Database\Log;
|
|||
use Chandler\Database\Logs;
|
||||
use openvk\Web\Models\Entities\{Voucher, Gift, GiftCategory, User, BannedLink};
|
||||
use openvk\Web\Models\Repositories\{Audios,
|
||||
Bans,
|
||||
ChandlerGroups,
|
||||
ChandlerUsers,
|
||||
Photos,
|
||||
Posts,
|
||||
Users,
|
||||
Clubs,
|
||||
Videos,
|
||||
Util\EntityStream,
|
||||
Vouchers,
|
||||
Gifts,
|
||||
BannedLinks};
|
||||
BannedLinks,
|
||||
Bans,
|
||||
Photos,
|
||||
Posts,
|
||||
Videos};
|
||||
use Chandler\Database\DatabaseConnection;
|
||||
|
||||
final class AdminPresenter extends OpenVKPresenter
|
||||
|
@ -39,7 +39,7 @@ final class AdminPresenter extends OpenVKPresenter
|
|||
$this->chandlerGroups = $chandlerGroups;
|
||||
$this->audios = $audios;
|
||||
$this->logs = DatabaseConnection::i()->getContext()->table("ChandlerLogs");
|
||||
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
|
|
|
@ -40,10 +40,9 @@ final class AudioPresenter extends OpenVKPresenter
|
|||
|
||||
function renderList(?int $owner = NULL, ?string $mode = "list"): void
|
||||
{
|
||||
$this->template->_template = "Audio/List";
|
||||
|
||||
$this->template->_template = "Audio/List.xml";
|
||||
$page = (int)($this->queryParam("p") ?? 1);
|
||||
$audios = [];
|
||||
$playlists = [];
|
||||
|
||||
if ($mode === "list") {
|
||||
$entity = NULL;
|
||||
|
@ -52,15 +51,15 @@ final class AudioPresenter extends OpenVKPresenter
|
|||
if (!$entity || $entity->isBanned())
|
||||
$this->redirect("/audios" . $this->user->id);
|
||||
|
||||
$audios = $this->audios->getByClub($entity);
|
||||
$playlists = $this->audios->getPlaylistsByClub($entity);
|
||||
$audios = $this->audios->getByClub($entity, $page, 10);
|
||||
$audiosCount = $this->audios->getClubCollectionSize($entity);
|
||||
} else {
|
||||
$entity = (new Users)->get($owner);
|
||||
if (!$entity || $entity->isDeleted() || $entity->isBanned())
|
||||
$this->redirect("/audios" . $this->user->id);
|
||||
|
||||
$audios = $this->audios->getByUser($entity);
|
||||
$playlists = $this->audios->getPlaylistsByUser($entity);
|
||||
$audios = $this->audios->getByUser($entity, $page, 10);
|
||||
$audiosCount = $this->audios->getUserCollectionSize($entity);
|
||||
}
|
||||
|
||||
if (!$entity)
|
||||
|
@ -71,16 +70,20 @@ final class AudioPresenter extends OpenVKPresenter
|
|||
$this->template->isMyClub = ($owner < 0 && $entity->canBeModifiedBy($this->user->identity));
|
||||
} else if ($mode === "new") {
|
||||
$audios = $this->audios->getNew();
|
||||
$audiosCount = $audios->size();
|
||||
} else {
|
||||
$audios = $this->audios->getPopular();
|
||||
$audiosCount = $audios->size();
|
||||
}
|
||||
|
||||
// $this->renderApp("owner=$owner");
|
||||
if ($audios !== [])
|
||||
if ($audios !== []) {
|
||||
$this->template->audios = iterator_to_array($audios);
|
||||
$this->template->audiosCount = $audiosCount;
|
||||
}
|
||||
|
||||
if ($playlists !== [])
|
||||
$this->template->playlists = iterator_to_array($playlists);
|
||||
$this->template->mode = $mode;
|
||||
$this->template->page = $page;
|
||||
}
|
||||
|
||||
function renderView(int $owner, int $id): void
|
||||
|
@ -169,7 +172,7 @@ final class AudioPresenter extends OpenVKPresenter
|
|||
$name = $this->postParam("name");
|
||||
$lyrics = $this->postParam("lyrics");
|
||||
$genre = empty($this->postParam("genre")) ? "undefined" : $this->postParam("genre");
|
||||
$nsfw = ($this->postParam("nsfw") ?? "off") === "on";
|
||||
$nsfw = ($this->postParam("explicit") ?? "off") === "on";
|
||||
if(empty($performer) || empty($name) || iconv_strlen($performer . $name) > 128) # FQN of audio must not be more than 128 chars
|
||||
$this->flashFail("err", tr("error"), tr("error_insufficient_info"));
|
||||
|
||||
|
@ -212,12 +215,7 @@ final class AudioPresenter extends OpenVKPresenter
|
|||
|
||||
function renderSearch(): void
|
||||
{
|
||||
if ($this->queryParam("q")) {
|
||||
$this->template->q = $this->queryParam("q");
|
||||
$this->template->by_performer = $this->queryParam("by_performer") === "on";
|
||||
$this->template->audios = iterator_to_array($this->audios->search($this->template->q, 1, $this->template->by_performer));
|
||||
$this->template->playlists = iterator_to_array($this->audios->searchPlaylists($this->template->q));
|
||||
}
|
||||
$this->redirect("/search?type=audios");
|
||||
}
|
||||
|
||||
function renderNewPlaylist(): void
|
||||
|
@ -348,30 +346,46 @@ final class AudioPresenter extends OpenVKPresenter
|
|||
|
||||
function renderAction(int $audio_id): void
|
||||
{
|
||||
$this->assertUserLoggedIn();
|
||||
$this->willExecuteWriteAction(true);
|
||||
$this->assertNoCSRF();
|
||||
|
||||
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
|
||||
header("HTTP/1.1 405 Method Not Allowed");
|
||||
exit(":)");
|
||||
}
|
||||
|
||||
$audio = $this->audios->get($audio_id);
|
||||
|
||||
if(!$audio || $audio->isDeleted())
|
||||
$this->flashFail("err", "error", tr("invalid_audio"), null, true);
|
||||
|
||||
switch ($this->queryParam("act")) {
|
||||
case "add":
|
||||
if (!$this->audios->isAdded($this->user->id, $audio_id)) {
|
||||
DatabaseConnection::i()->getContext()->query("INSERT INTO `audio_relations` (`entity`, `audio`) VALUES (?, ?)", $this->user->id, $audio_id);
|
||||
} else {
|
||||
$this->returnJson(["success" => false, "error" => "Аудиозапись уже добавлена"]);
|
||||
}
|
||||
if($audio->isWithdrawn())
|
||||
$this->flashFail("err", "error", tr("invalid_audio"), null, true);
|
||||
|
||||
if(!$audio->isInLibraryOf($this->user->identity))
|
||||
$audio->add($this->user->identity);
|
||||
else
|
||||
$this->flashFail("err", "error", tr("do_have_audio"), null, true);
|
||||
|
||||
break;
|
||||
|
||||
case "remove":
|
||||
if ($this->audios->isAdded($this->user->id, $audio_id)) {
|
||||
DatabaseConnection::i()->getContext()->query("DELETE FROM `audio_relations` WHERE `entity` = ? AND `audio` = ?", $this->user->id, $audio_id);
|
||||
} else {
|
||||
$this->returnJson(["success" => false, "error" => "Аудиозапись не добавлена"]);
|
||||
}
|
||||
break;
|
||||
if($audio->isInLibraryOf($this->user->identity))
|
||||
$audio->remove($this->user->identity);
|
||||
else
|
||||
$this->flashFail("err", "error", tr("do_not_have_audio"), null, true);
|
||||
|
||||
break;
|
||||
case "edit":
|
||||
$audio = $this->audios->get($audio_id);
|
||||
if (!$audio || $audio->isDeleted() || $audio->isWithdrawn() || $audio->isUnlisted())
|
||||
$this->returnJson(["success" => false, "error" => "Аудиозапись не найдена"]);
|
||||
$this->flashFail("err", "error", tr("invalid_audio"), null, true);
|
||||
|
||||
if ($audio->getOwner()->getId() !== $this->user->id)
|
||||
$this->returnJson(["success" => false, "error" => "Ошибка доступа"]);
|
||||
$this->flashFail("err", "error", tr("access_denied"), null, true);
|
||||
|
||||
$performer = $this->postParam("performer");
|
||||
$name = $this->postParam("name");
|
||||
|
@ -379,13 +393,22 @@ final class AudioPresenter extends OpenVKPresenter
|
|||
$genre = empty($this->postParam("genre")) ? "undefined" : $this->postParam("genre");
|
||||
$nsfw = ($this->postParam("nsfw") ?? "off") === "on";
|
||||
if(empty($performer) || empty($name) || iconv_strlen($performer . $name) > 128) # FQN of audio must not be more than 128 chars
|
||||
$this->flashFail("err", tr("error"), tr("error_insufficient_info"));
|
||||
$this->flashFail("err", tr("error"), tr("error_insufficient_info"), null, true);
|
||||
|
||||
$audio->setName($name);
|
||||
$audio->setPerformer($performer);
|
||||
$audio->setLyrics(empty($lyrics) ? NULL : $lyrics);
|
||||
$audio->setGenre($genre);
|
||||
$audio->save();
|
||||
|
||||
$this->returnJson(["success" => true, "new_info" => [
|
||||
"name" => ovk_proc_strtr($audio->getTitle(), 40),
|
||||
"performer" => ovk_proc_strtr($audio->getPerformer(), 40),
|
||||
"lyrics" => nl2br($audio->getLyrics() ?? ""),
|
||||
"lyrics_unformatted" => $audio->getLyrics() ?? "",
|
||||
"explicit" => $audio->isExplicit(),
|
||||
"genre" => $audio->getGenre(),
|
||||
]]);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -394,4 +417,9 @@ final class AudioPresenter extends OpenVKPresenter
|
|||
|
||||
$this->returnJson(["success" => true]);
|
||||
}
|
||||
|
||||
function renderPlaylists(int $owner)
|
||||
{
|
||||
$this->assertUserLoggedIn();
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Presenters;
|
||||
use openvk\Web\Models\Entities\{User, Club};
|
||||
use openvk\Web\Models\Repositories\{Users, Clubs, Posts, Comments, Videos, Applications, Notes};
|
||||
use openvk\Web\Models\Repositories\{Users, Clubs, Posts, Comments, Videos, Applications, Notes, Audios};
|
||||
use Chandler\Database\DatabaseConnection;
|
||||
|
||||
final class SearchPresenter extends OpenVKPresenter
|
||||
|
@ -13,6 +13,7 @@ final class SearchPresenter extends OpenVKPresenter
|
|||
private $videos;
|
||||
private $apps;
|
||||
private $notes;
|
||||
private $audios;
|
||||
|
||||
function __construct(Users $users, Clubs $clubs)
|
||||
{
|
||||
|
@ -23,22 +24,21 @@ final class SearchPresenter extends OpenVKPresenter
|
|||
$this->videos = new Videos;
|
||||
$this->apps = new Applications;
|
||||
$this->notes = new Notes;
|
||||
$this->audios = new Audios;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
function renderIndex(): void
|
||||
{
|
||||
$this->assertUserLoggedIn();
|
||||
|
||||
$query = $this->queryParam("query") ?? "";
|
||||
$type = $this->queryParam("type") ?? "users";
|
||||
$sorter = $this->queryParam("sort") ?? "id";
|
||||
$invert = $this->queryParam("invert") == 1 ? "ASC" : "DESC";
|
||||
$page = (int) ($this->queryParam("p") ?? 1);
|
||||
|
||||
$this->willExecuteWriteAction();
|
||||
if($query != "")
|
||||
$this->assertUserLoggedIn();
|
||||
|
||||
# https://youtu.be/pSAWM5YuXx8
|
||||
|
||||
$repos = [
|
||||
|
@ -47,7 +47,7 @@ final class SearchPresenter extends OpenVKPresenter
|
|||
"posts" => "posts",
|
||||
"comments" => "comments",
|
||||
"videos" => "videos",
|
||||
"audios" => "posts",
|
||||
"audios" => "audios",
|
||||
"apps" => "apps",
|
||||
"notes" => "notes"
|
||||
];
|
||||
|
@ -62,7 +62,17 @@ final class SearchPresenter extends OpenVKPresenter
|
|||
break;
|
||||
case "rating":
|
||||
$sort = "rating " . $invert;
|
||||
break;
|
||||
break;
|
||||
case "length":
|
||||
if($type != "audios") break;
|
||||
|
||||
$sort = "length " . $invert;
|
||||
break;
|
||||
case "listens":
|
||||
if($type != "audios") break;
|
||||
|
||||
$sort = "listens " . $invert;
|
||||
break;
|
||||
}
|
||||
|
||||
$parameters = [
|
||||
|
@ -86,7 +96,9 @@ final class SearchPresenter extends OpenVKPresenter
|
|||
"hometown" => $this->queryParam("hometown") != "" ? $this->queryParam("hometown") : NULL,
|
||||
"before" => $this->queryParam("datebefore") != "" ? strtotime($this->queryParam("datebefore")) : NULL,
|
||||
"after" => $this->queryParam("dateafter") != "" ? strtotime($this->queryParam("dateafter")) : NULL,
|
||||
"gender" => $this->queryParam("gender") != "" && $this->queryParam("gender") != 2 ? $this->queryParam("gender") : NULL
|
||||
"gender" => $this->queryParam("gender") != "" && $this->queryParam("gender") != 2 ? $this->queryParam("gender") : NULL,
|
||||
"only_performers" => $this->queryParam("only_performers") == "on" ? "1" : NULL,
|
||||
"with_lyrics" => $this->queryParam("with_lyrics") == "on" ? true : NULL,
|
||||
];
|
||||
|
||||
$repo = $repos[$type] or $this->throwError(400, "Bad Request", "Invalid search entity $type.");
|
||||
|
|
|
@ -5,7 +5,7 @@ use openvk\Web\Util\Sms;
|
|||
use openvk\Web\Themes\Themepacks;
|
||||
use openvk\Web\Models\Entities\{Photo, Post, EmailChangeVerification};
|
||||
use openvk\Web\Models\Entities\Notifications\{CoinsTransferNotification, RatingUpNotification};
|
||||
use openvk\Web\Models\Repositories\{Users, Clubs, Albums, Videos, Notes, Vouchers, EmailChangeVerifications};
|
||||
use openvk\Web\Models\Repositories\{Users, Clubs, Albums, Videos, Notes, Vouchers, EmailChangeVerifications, Audios};
|
||||
use openvk\Web\Models\Exceptions\InvalidUserNameException;
|
||||
use openvk\Web\Util\Validator;
|
||||
use Chandler\Security\Authenticator;
|
||||
|
@ -45,6 +45,8 @@ final class UserPresenter extends OpenVKPresenter
|
|||
$this->template->videosCount = (new Videos)->getUserVideosCount($user);
|
||||
$this->template->notes = (new Notes)->getUserNotes($user, 1, 4);
|
||||
$this->template->notesCount = (new Notes)->getUserNotesCount($user);
|
||||
$this->template->audios = (new Audios)->getByUser($user, 1, 3);
|
||||
$this->template->audiosCount = (new Audios)->getUserCollectionSize($user);
|
||||
|
||||
$this->template->user = $user;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
{script "js/node_modules/umbrellajs/umbrella.min.js"}
|
||||
{script "js/l10n.js"}
|
||||
{script "js/openvk.cls.js"}
|
||||
{script "js/node_modules/dashjs/dist/dash.all.min.js"}
|
||||
{script "js/al_music.js"}
|
||||
|
||||
{css "js/node_modules/tippy.js/dist/backdrop.css"}
|
||||
{css "js/node_modules/tippy.js/dist/border.css"}
|
||||
|
@ -122,6 +124,7 @@
|
|||
<option value="comments">{_s_by_comments}</option>
|
||||
<option value="videos">{_s_by_videos}</option>
|
||||
<option value="apps">{_s_by_apps}</option>
|
||||
<option value="audios">{_s_by_audios}</option>
|
||||
</select>
|
||||
</form>
|
||||
<div class="searchTips" id="srcht" hidden>
|
||||
|
@ -140,13 +143,13 @@
|
|||
<option value="comments" {if str_contains($_SERVER['REQUEST_URI'], "type=comments")}selected{/if}>{_s_by_comments}</option>
|
||||
<option value="videos" {if str_contains($_SERVER['REQUEST_URI'], "type=videos")}selected{/if}>{_s_by_videos}</option>
|
||||
<option value="apps" {if str_contains($_SERVER['REQUEST_URI'], "type=apps")}selected{/if}>{_s_by_apps}</option>
|
||||
<option value="audios" {if str_contains($_SERVER['REQUEST_URI'], "type=audios")}selected{/if}>{_s_by_audios}</option>
|
||||
</select>
|
||||
<button class="searchBtn"><span style="color:white;font-weight: 600;font-size:12px;">{_header_search}</span></button>
|
||||
</form>
|
||||
<script>
|
||||
let els = document.querySelectorAll("div.dec")
|
||||
for(const element of els)
|
||||
{
|
||||
for(const element of els) {
|
||||
element.style.display = "none"
|
||||
}
|
||||
</script>
|
||||
|
@ -181,7 +184,7 @@
|
|||
</object>
|
||||
</a>
|
||||
<a n:if="$thisUser->getLeftMenuItemStatus('photos')" href="/albums{$thisUser->getId()}" class="link">{_my_photos}</a>
|
||||
<a n:if="$thisUser->getLeftMenuItemStatus('audios')" href="/audios{$thisUser->getId()}" class="link">Мои Аудиозаписи</a>
|
||||
<a n:if="$thisUser->getLeftMenuItemStatus('audios')" href="/audios{$thisUser->getId()}" class="link">{_my_audios}</a>
|
||||
<a n:if="$thisUser->getLeftMenuItemStatus('videos')" href="/videos{$thisUser->getId()}" class="link">{_my_videos}</a>
|
||||
<a n:if="$thisUser->getLeftMenuItemStatus('messages')" href="/im" class="link">{_my_messages}
|
||||
<object type="internal/link" n:if="$thisUser->getUnreadMessagesCount() > 0">
|
||||
|
@ -427,6 +430,12 @@
|
|||
//]]>
|
||||
</script>
|
||||
|
||||
<script>
|
||||
window.openvk = {
|
||||
"audio_genres": {\openvk\Web\Models\Entities\Audio::genres}
|
||||
}
|
||||
</script>
|
||||
|
||||
{ifset bodyScripts}
|
||||
{include bodyScripts}
|
||||
{/ifset}
|
||||
|
|
|
@ -16,19 +16,19 @@
|
|||
<input class="text medium-field" type="number" id="id" disabled value="{$audio->getId()}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="name">Название</label>
|
||||
<label for="name">{_name}</label>
|
||||
<input class="text medium-field" type="text" id="name" name="name" value="{$audio->getTitle()}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="performer">Исполнитель</label>
|
||||
<label for="performer">{_performer}</label>
|
||||
<input class="text medium-field" type="text" id="performer" name="performer" value="{$audio->getPerformer()}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="ext">Текст</label>
|
||||
<label for="ext">{_lyrics}</label>
|
||||
<textarea class="text medium-field" type="text" id="text" name="text" style="resize: vertical;">{$audio->getLyrics()}</textarea>
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="ext">Жанр</label>
|
||||
<label for="ext">{_genre}</label>
|
||||
<select class="select medium-field" name="genre">
|
||||
<option n:foreach='\openvk\Web\Models\Entities\Audio::genres as $genre'
|
||||
n:attr="selected: $genre == $audio->getGenre()" value="{$genre}">
|
||||
|
@ -38,7 +38,7 @@
|
|||
</div>
|
||||
<hr />
|
||||
<div class="field-group">
|
||||
<label for="owner">Владелец</label>
|
||||
<label for="owner">{_owner}</label>
|
||||
<input class="text medium-field" type="number" id="owner_id" name="owner" value="{$audio->getOwner()->getId()}" />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
|
@ -46,11 +46,11 @@
|
|||
<input class="toggle-large" type="checkbox" id="explicit" name="explicit" value="1" {if $audio->isExplicit()} checked {/if} />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="deleted">Удалено</label>
|
||||
<label for="deleted">{_deleted}</label>
|
||||
<input class="toggle-large" type="checkbox" id="deleted" name="deleted" value="1" {if $audio->isDeleted()} checked {/if} />
|
||||
</div>
|
||||
<div class="field-group">
|
||||
<label for="withdrawn">Изъято</label>
|
||||
<label for="withdrawn">{_withdrawn}</label>
|
||||
<input class="toggle-large" type="checkbox" id="withdrawn" name="withdrawn" value="1" {if $audio->isWithdrawn()} checked {/if} />
|
||||
</div>
|
||||
<hr />
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
{var $search = $mode === "audios"}
|
||||
|
||||
{block title}
|
||||
Поиск музыки
|
||||
{_audios}
|
||||
{/block}
|
||||
|
||||
{block heading}
|
||||
Музыка
|
||||
{_audios}
|
||||
{/block}
|
||||
|
||||
{block searchTitle}
|
||||
|
@ -19,10 +19,10 @@
|
|||
<div class="aui-navgroup-primary">
|
||||
<ul class="aui-nav" resolved="">
|
||||
<li n:attr="class => $mode === 'audios' ? 'aui-nav-selected' : ''">
|
||||
<a href="?act=audios">Аудиозаписи</a>
|
||||
<a href="?act=audios">{_audios}</a>
|
||||
</li>
|
||||
<li n:attr="class => $mode === 'playlists' ? 'aui-nav-selected' : ''">
|
||||
<a href="?act=playlists">Плейлисты</a>
|
||||
<a href="?act=playlists">{_playlists}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -39,12 +39,12 @@
|
|||
<th>{_admin_author}</th>
|
||||
<th>Исполнитель</th>
|
||||
<th>{_admin_title}</th>
|
||||
<th>Жанр</th>
|
||||
<th>{_genre}</th>
|
||||
<th>Explicit</th>
|
||||
<th>Изъято</th>
|
||||
<th>Удалено</th>
|
||||
<th>Создан</th>
|
||||
<th>Действия</th>
|
||||
<th>{_withdrawn}</th>
|
||||
<th>{_deleted}</th>
|
||||
<th>{_created}</th>
|
||||
<th>{_actions}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -87,7 +87,7 @@
|
|||
<tr>
|
||||
<th>ID</th>
|
||||
<th>{_admin_author}</th>
|
||||
<th>Название</th>
|
||||
<th>{_name}</th>
|
||||
<th>Создан</th>
|
||||
<th>Действия</th>
|
||||
</tr>
|
||||
|
|
|
@ -1,86 +1,97 @@
|
|||
{extends "../@layout.xml"}
|
||||
|
||||
{block title}Аудиозаписи{/block}
|
||||
{block title}{_audios}{/block}
|
||||
|
||||
{block header}
|
||||
<div>
|
||||
<div n:if="$isMy">Мои аудиозаписи</div>
|
||||
<div n:if="!$isMy">
|
||||
<a href="{$owner->getURL()}">{$owner->getCanonicalName()}</a>
|
||||
»
|
||||
Аудиозаписи
|
||||
</div>
|
||||
<div n:if="$mode == 'list'">
|
||||
<div n:if="$isMy">{_my_audios_small}</div>
|
||||
<div n:if="!$isMy">
|
||||
<a href="{$owner->getURL()}">{$owner->getCanonicalName()}</a>
|
||||
»
|
||||
{_audios}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div n:if="$mode == 'new'">
|
||||
{_audios}
|
||||
»
|
||||
{_audio_new}
|
||||
</div>
|
||||
|
||||
<div n:if="$mode == 'popular'">
|
||||
{_audios}
|
||||
»
|
||||
{_audio_popular}
|
||||
</div>
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
{include "tabs.xml", mode => "list", listText => ($isMy ? "Моя музыка" : $owner->getCanonicalName())}
|
||||
|
||||
<style>
|
||||
.playlist-name {
|
||||
max-width: 100px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
font-family: -apple-system, system-ui, "Helvetica Neue", Roboto, sans-serif;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.playlist-name:hover { text-decoration: underline; }
|
||||
</style>
|
||||
<div n:if="count($playlists) > 0 || count($audios) > 0 || ($isMy || $isMyClub)">
|
||||
<h4 style="padding: 8px; display: flex; justify-content: space-between;">
|
||||
<div>Плейлисты</div>
|
||||
<div n:if="$isMy || $isMyClub">
|
||||
<div class="icon add-icon" onClick="window.location.href = '/audios/newPlaylist'" />
|
||||
{* ref: https://archive.li/P32em *}
|
||||
<div class="bigPlayer">
|
||||
<div class="paddingLayer">
|
||||
<div class="playButtons">
|
||||
<div class="playButton musicIcon"></div>
|
||||
|
||||
<div class="arrowsButtons">
|
||||
<div class="nextButton musicIcon"></div>
|
||||
<div class="backButton musicIcon"></div>
|
||||
</div>
|
||||
</div>
|
||||
</h4>
|
||||
<div style="padding: 8px;">
|
||||
<div n:if="count($playlists) <= 0">
|
||||
{include "../components/nothing.xml"}
|
||||
</div>
|
||||
<div n:if="count($playlists) > 0" style="display: flex; gap: 8px; overflow-x: auto;">
|
||||
<div n:foreach="$playlists as $playlist">
|
||||
<div style="cursor: pointer;" onClick="window.location.href = '/playlist{$playlist->getOwner()->getId()}_{$playlist->getId()}'">
|
||||
<div><img src="{$playlist->getCoverURL()}" width="100" height="100" style="border-radius: 8px;" /></div>
|
||||
<div class="playlist-name">{$playlist->getName()}</div>
|
||||
|
||||
<div class="trackPanel">
|
||||
<div class="trackInfo">
|
||||
<b>{_track_unknown}</b> —
|
||||
<span>{_track_noname}</span>
|
||||
|
||||
<span class="time">00:00</span>
|
||||
</div>
|
||||
|
||||
<div class="selectableTrack">
|
||||
<div>
|
||||
<div class="slider"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 style="padding: 8px; display: flex; justify-content: space-between;">
|
||||
<div>Музыка</div>
|
||||
<div n:if="$isMy || $isMyClub">
|
||||
<div class="icon add-icon" onClick="window.location.href = '/player/upload'" />
|
||||
</div>
|
||||
</h4>
|
||||
<div style="padding: 8px;">
|
||||
<div n:if="count($audios) <= 0">
|
||||
{include "../components/nothing.xml"}
|
||||
</div>
|
||||
<div n:if="count($audios) > 0">
|
||||
{foreach $audios as $audio}
|
||||
{include "player.xml",
|
||||
audio => $audio,
|
||||
canAdd => !$isMy && !isMyClub,
|
||||
canRemove => $isMy || !isMyClub,
|
||||
canEdit => $audio->getOwner()->getId() === $thisUser->getId() || !isMyClub,
|
||||
deleteOnClick => "removeAudio({$audio->getId()})",
|
||||
editOnClick => "editAudio({$audio->getId()}, `{$audio->getTitle()}`, `{$audio->getPerformer()}`, `{$audio->getGenre()}`, `{$audio->getLyrics()}`)"
|
||||
}
|
||||
{/foreach}
|
||||
<div class="volumePanel">
|
||||
<div class="selectableTrack">
|
||||
<div>
|
||||
<div class="slider"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div n:if="count($audios) > 0">
|
||||
{script "js/node_modules/umbrellajs/umbrella.min.js"}
|
||||
{script "js/node_modules/dashjs/dist/dash.all.min.js"}
|
||||
|
||||
{include "player.js.xml", audios => $audios}
|
||||
<div class="additionalButtons">
|
||||
<div class="repeatButton musicIcon"></div>
|
||||
<div class="shuffleButton musicIcon"></div>
|
||||
<div class="deviceButton musicIcon"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div n:if="count($playlists) === 0 && count($audios) === 0 && !($isMy || $isMyClub)" style="padding: 8px;">
|
||||
{include "../components/nothing.xml"}
|
||||
|
||||
<div style="width: 100%;display: flex;margin-bottom: -10px;">
|
||||
<div style="width: 74%;">
|
||||
<div style="padding: 8px;">
|
||||
<div n:if="$audiosCount <= 0">
|
||||
{include "../components/nothing.xml"}
|
||||
</div>
|
||||
<div n:if="$audiosCount > 0" class="infContainer">
|
||||
<div class="infObj" n:foreach="$audios as $audio">
|
||||
{include "player.xml", audio => $audio}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{include "../components/paginator.xml", conf => (object) [
|
||||
"page" => $page,
|
||||
"count" => $audiosCount,
|
||||
"amount" => sizeof($audios),
|
||||
"perPage" => $perPage ?? OPENVK_DEFAULT_PER_PAGE,
|
||||
"atBottom" => true,
|
||||
]}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{include "tabs.xml"}
|
||||
</div>
|
||||
{/block}
|
||||
{/block}
|
|
@ -1,29 +0,0 @@
|
|||
{extends "../@layout.xml"}
|
||||
|
||||
{block title}Аудиозаписи{/block}
|
||||
|
||||
{block header}
|
||||
Новое
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
{include "tabs.xml", mode => "new"}
|
||||
|
||||
<div style="padding: 8px;">
|
||||
<div n:if="count($audios) <= 0">
|
||||
{include "../components/nothing.xml"}
|
||||
</div>
|
||||
<div n:if="count($audios) > 0">
|
||||
{foreach $audios as $audio}
|
||||
{include "player.xml", audio => $audio, addOnClick => "addAudio({$audio->getId()})"}
|
||||
{/foreach}
|
||||
</div>
|
||||
|
||||
<div n:if="count($audios) > 0">
|
||||
{script "js/node_modules/umbrellajs/umbrella.min.js"}
|
||||
{script "js/node_modules/dashjs/dist/dash.all.min.js"}
|
||||
|
||||
{include "player.js.xml", audios => $audios}
|
||||
</div>
|
||||
</div>
|
||||
{/block}
|
15
Web/Presenters/templates/Audio/Playlists.xml
Normal file
15
Web/Presenters/templates/Audio/Playlists.xml
Normal file
|
@ -0,0 +1,15 @@
|
|||
{extends "../@layout.xml"}
|
||||
|
||||
{block title}{_audios}{/block}
|
||||
|
||||
{block header}
|
||||
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
{script "js/node_modules/umbrellajs/umbrella.min.js"}
|
||||
|
||||
<div style="width: 100%;display: flex;margin-bottom: -10px;">
|
||||
{include "tabs.xml"}
|
||||
</div>
|
||||
{/block}
|
|
@ -1,29 +0,0 @@
|
|||
{extends "../@layout.xml"}
|
||||
|
||||
{block title}Аудиозаписи{/block}
|
||||
|
||||
{block header}
|
||||
Популярное
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
{include "tabs.xml", mode => "popular"}
|
||||
|
||||
<div style="padding: 8px;">
|
||||
<div n:if="count($audios) <= 0">
|
||||
{include "../components/nothing.xml"}
|
||||
</div>
|
||||
<div n:if="count($audios) > 0">
|
||||
{foreach $audios as $audio}
|
||||
{include "player.xml", audio => $audio, addOnClick => "addAudio({$audio->getId()})"}
|
||||
{/foreach}
|
||||
</div>
|
||||
|
||||
<div n:if="count($audios) > 0">
|
||||
{script "js/node_modules/umbrellajs/umbrella.min.js"}
|
||||
{script "js/node_modules/dashjs/dist/dash.all.min.js"}
|
||||
|
||||
{include "player.js.xml", audios => $audios}
|
||||
</div>
|
||||
</div>
|
||||
{/block}
|
|
@ -1,77 +0,0 @@
|
|||
{extends "../@layout.xml"}
|
||||
|
||||
{block title}Аудиозаписи{/block}
|
||||
|
||||
{block header}
|
||||
Поиск
|
||||
{/block}
|
||||
|
||||
{block content}
|
||||
|
||||
{include "tabs.xml", mode => "search"}
|
||||
|
||||
<div style="padding: 8px;">
|
||||
<form>
|
||||
<input n:attr="value => $q" name="q" type="text" placeholder="Поиск..." />
|
||||
<div style="display: flex; justify-content: space-between; padding: 8px 0;">
|
||||
<div><input n:attr="checked => $by_performer" type="checkbox" name="by_performer" /> исполнитель</div>
|
||||
<button class="button">Найти</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div n:if="$q && (count($audios) > 0 || count($playlists) > 0)">
|
||||
<style>
|
||||
.playlist-name {
|
||||
max-width: 100px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
font-family: -apple-system, system-ui, "Helvetica Neue", Roboto, sans-serif;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.playlist-name:hover { text-decoration: underline; }
|
||||
</style>
|
||||
<h4 style="padding: 8px;">Плейлисты</h4>
|
||||
<div style="padding: 8px;">
|
||||
<div n:if="count($playlists) <= 0">
|
||||
{include "../components/nothing.xml"}
|
||||
</div>
|
||||
<div n:if="count($playlists) > 0" style="display: flex; gap: 8px; overflow-x: auto;">
|
||||
<div n:foreach="$playlists as $playlist">
|
||||
<div style="cursor: pointer;"
|
||||
onClick="window.location.href = '/playlist{$playlist->getOwner()->getId()}_{$playlist->getId()}'">
|
||||
<div>
|
||||
<img src="{$playlist->getCoverURL()}" width="100" height="100"
|
||||
style="border-radius: 8px;"/>
|
||||
</div>
|
||||
<div class="playlist-name">{$playlist->getName()}</div>
|
||||
<a href="{$playlist->getOwner()->getURL()}" class="playlist-name" style="font-weight: 400;">
|
||||
{$playlist->getOwner()->getCanonicalName()}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 style="padding: 8px;">Треки</h4>
|
||||
<div n:if="count($audios) <= 0">
|
||||
{include "../components/nothing.xml"}
|
||||
</div>
|
||||
<div n:if="count($audios) > 0" n:foreach="$audios as $audio">
|
||||
{include "player.xml", audio => $audio, addOnClick => "addAudio({$audio->getId()})"}
|
||||
<br/>
|
||||
</div>
|
||||
|
||||
<div n:if="count($audios) > 0">
|
||||
{script "js/node_modules/umbrellajs/umbrella.min.js"}
|
||||
{script "js/node_modules/dashjs/dist/dash.all.min.js"}
|
||||
|
||||
{include "player.js.xml", audios => $audios}
|
||||
</div>
|
||||
</div>
|
||||
<div n:if="count($playlists) <= 0 && count($audios) <= 0">
|
||||
{include "../components/nothing.xml"}
|
||||
</div>
|
||||
</div>
|
||||
{/block}
|
|
@ -20,106 +20,134 @@
|
|||
{/block}
|
||||
|
||||
{block content}
|
||||
<div class="container_gray" style="background: white; border: 0;">
|
||||
<div class="container_gray" style="border: 0;margin-top: -10px;">
|
||||
<div id="upload_container">
|
||||
<h4>{_select_audio}</h4><br/>
|
||||
<b><a href="javascript:false">{_limits}</a></b>
|
||||
<ul>
|
||||
<li>{tr("audio_requirements", 1, 30, 25)}</li>
|
||||
</ul>
|
||||
<div id="audio_upload">
|
||||
<form enctype="multipart/form-data" method="POST">
|
||||
<input type="hidden" name="name" />
|
||||
<input type="hidden" name="performer" />
|
||||
<input type="hidden" name="lyrics" />
|
||||
<input type="hidden" name="genre" />
|
||||
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<input id="audio_input" type="file" name="blob" accept="audio/*" />
|
||||
</form>
|
||||
</div><br/>
|
||||
|
||||
<span>{_you_can_also_add_audio_using} <b><a href="/player">{_search_audio_inst}</a></b>.<span>
|
||||
<div id="firstStep">
|
||||
<h4>{_select_audio}</h4><br/>
|
||||
<b><a href="javascript:false">{_limits}</a></b>
|
||||
<ul>
|
||||
<li>{tr("audio_requirements", 1, 30, 25)}</li>
|
||||
</ul>
|
||||
<div id="audio_upload">
|
||||
<form enctype="multipart/form-data" method="POST">
|
||||
<input type="hidden" name="name" />
|
||||
<input type="hidden" name="performer" />
|
||||
<input type="hidden" name="lyrics" />
|
||||
<input type="hidden" name="genre" />
|
||||
<input type="hidden" name="explicit" />
|
||||
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<input id="audio_input" type="file" name="blob" accept="audio/*" style="display:none" />
|
||||
<input value="{_upload_button}" class="button" type="button" onclick="document.querySelector('#audio_input').click()">
|
||||
</form>
|
||||
</div><br/>
|
||||
|
||||
<span>{_you_can_also_add_audio_using} <b><a href="/player">{_search_audio_inst}</a></b>.<span>
|
||||
</div>
|
||||
|
||||
<div id="lastStep" style="display:none;">
|
||||
<table cellspacing="7" cellpadding="0" border="0" align="center">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="120" valign="top"><span class="nobold">{_audio_name}:</span></td>
|
||||
<td><input type="text" name="name" autocomplete="off" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="120" valign="top"><span class="nobold">{_performer}:</span></td>
|
||||
<td><input name="performer" type="text" autocomplete="off" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="120" valign="top"><span class="nobold">{_genre}:</span></td>
|
||||
<td>
|
||||
<select name="genre">
|
||||
<option n:foreach='\openvk\Web\Models\Entities\Audio::genres as $genre' n:attr="selected: $genre == 'Other'" value="{$genre}">
|
||||
{$genre}
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="120" valign="top"><span class="nobold">{_lyrics}:</span></td>
|
||||
<td><textarea name="lyrics" style="resize: vertical;max-height: 300px;"></textarea></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="120" valign="top"></td>
|
||||
<td>
|
||||
<label><input type="checkbox" name="explicit">{_audios_explicit}</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="120" valign="top"></td>
|
||||
<td>
|
||||
<input class="button" type="button" id="uploadMuziko" value="{_upload_button}">
|
||||
<input class="button" type="button" id="backToUpload" value="{_select_another_file}">
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="dialogBoxHtml" style="display: none;">
|
||||
<table cellspacing="7" cellpadding="0" border="0" align="center">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="120" valign="top"><span class="nobold">Имя:</span></td>
|
||||
<td><input type="text" name="name" autocomplete="off" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="120" valign="top"><span class="nobold">Исполнитель:</span></td>
|
||||
<td><input name="performer" autocomplete="off" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="120" valign="top"><span class="nobold">Жанр:</span></td>
|
||||
<td>
|
||||
<select name="genre">
|
||||
<option n:foreach='\openvk\Web\Models\Entities\Audio::genres as $genre' n:attr="selected: $genre == 'Other'" value="{$genre}">
|
||||
{$genre}
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="120" valign="top"><span class="nobold">Текст:</span></td>
|
||||
<td><textarea name="lyrics"></textarea></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<script type="module">
|
||||
import * as id3 from "/assets/packages/static/openvk/js/node_modules/id3js/lib/id3.js";
|
||||
|
||||
u("#audio_input").on("change", async function(e) {
|
||||
if(e.currentTarget.files.length <= 0)
|
||||
let files = e.currentTarget.files
|
||||
if(files.length <= 0)
|
||||
return;
|
||||
|
||||
var name_ = document.querySelector("#audio_upload input[name=name]");
|
||||
var perf_ = document.querySelector("#audio_upload input[name=performer]");
|
||||
var genre_ = document.querySelector("#audio_upload input[name=genre]");
|
||||
var lyrics_ = document.querySelector("#audio_upload input[name=lyrics]");
|
||||
document.querySelector("#firstStep").style.display = "none"
|
||||
document.querySelector("#lastStep").style.display = "block"
|
||||
|
||||
MessageBox({_upload_audio}, document.querySelector("#dialogBoxHtml").innerHTML, [{_ok}, {_cancel}], [
|
||||
function() {
|
||||
var name = u("input[name=name]", this.$dialog().nodes[0]).nodes[0].value;
|
||||
var perf = u("input[name=performer]", this.$dialog().nodes[0]).nodes[0].value;
|
||||
var genre = u("select[name=genre]", this.$dialog().nodes[0]).nodes[0].value;
|
||||
var lyrics = u("textarea[name=lyrics]", this.$dialog().nodes[0]).nodes[0].value;
|
||||
|
||||
name_.value = name;
|
||||
perf_.value = perf;
|
||||
genre_.value = genre;
|
||||
lyrics_.value = lyrics;
|
||||
|
||||
document.querySelector("#audio_upload > form").submit();
|
||||
},
|
||||
|
||||
Function.noop
|
||||
]);
|
||||
|
||||
let tags = await id3.fromFile(e.currentTarget.files[0]);
|
||||
let tags = await id3.fromFile(files[0]);
|
||||
if(tags != null) {
|
||||
console.log("ID" + tags.kind + " detected, setting values...");
|
||||
|
||||
if(tags.title != null)
|
||||
document.querySelector(".ovk-diag input[name=name]").value = tags.title;
|
||||
document.querySelector("#lastStep input[name=name]").value = tags.title;
|
||||
|
||||
if(tags.artist != null)
|
||||
document.querySelector(".ovk-diag input[name=performer]").value = tags.artist;
|
||||
document.querySelector("#lastStep input[name=performer]").value = tags.artist;
|
||||
|
||||
if(tags.genre != null) {
|
||||
if(document.querySelector(".ovk-diag select[name=genre] > option[value='" + tags.genre + "']") != null) {
|
||||
document.querySelector(".ovk-diag select[name=genre]").value = tags.genre;
|
||||
if(document.querySelector("#lastStep select[name=genre] > option[value='" + tags.genre + "']") != null) {
|
||||
document.querySelector("#lastStep select[name=genre]").value = tags.genre;
|
||||
} else {
|
||||
console.warn("Unknown genre: " + tags.genre);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
document.querySelector("#lastStep input[name=name]").value = files[0].name
|
||||
document.querySelector("#lastStep select[name=genre]").value = "Other"
|
||||
}
|
||||
});
|
||||
|
||||
u("#backToUpload").on("click", (e) => {
|
||||
document.querySelector("#firstStep").style.display = "block"
|
||||
document.querySelector("#lastStep").style.display = "none"
|
||||
|
||||
document.querySelector("#lastStep input[name=name]").value = ""
|
||||
document.querySelector("#lastStep input[name=performer]").value = ""
|
||||
document.querySelector("#lastStep select[name=genre]").value = ""
|
||||
document.querySelector("#lastStep textarea[name=lyrics]").value = ""
|
||||
document.querySelector("#audio_input").value = ""
|
||||
})
|
||||
|
||||
u("#uploadMuziko").on("click", (e) => {
|
||||
var name_ = document.querySelector("#audio_upload input[name=name]");
|
||||
var perf_ = document.querySelector("#audio_upload input[name=performer]");
|
||||
var genre_ = document.querySelector("#audio_upload input[name=genre]");
|
||||
var lyrics_ = document.querySelector("#audio_upload input[name=lyrics]");
|
||||
var explicit_ = document.querySelector("#audio_upload input[name=explicit]");
|
||||
|
||||
name_.value = document.querySelector("#lastStep input[name=name]").value
|
||||
perf_.value = document.querySelector("#lastStep input[name=performer]").value
|
||||
genre_.value = document.querySelector("#lastStep select[name=genre]").value
|
||||
lyrics_.value = document.querySelector("#lastStep textarea[name=lyrics]").value
|
||||
explicit_.value = document.querySelector("#lastStep input[name=explicit]").value
|
||||
|
||||
document.querySelector("#audio_upload > form").submit();
|
||||
})
|
||||
</script>
|
||||
{/block}
|
||||
|
|
|
@ -1,144 +0,0 @@
|
|||
<script>
|
||||
function fmtTime(time) {
|
||||
const mins = String(Math.floor(time / 60)).padStart(2, '0');
|
||||
const secs = String(Math.floor(time % 60)).padStart(2, '0');
|
||||
return `${ mins}:${ secs}`;
|
||||
}
|
||||
|
||||
function initPlayer(id, keys, url, length) {
|
||||
console.log(`INIT PLAYER ${ id}`, keys, url, length);
|
||||
const audio = document.querySelector(`#audioEmbed-${ id} .audio`);
|
||||
const playButton = u(`#audioEmbed-${ id} .playerButton > img`);
|
||||
const trackDiv = u(`#audioEmbed-${ id} .track > div > div`);
|
||||
const volumeSpan = u(`#audioEmbed-${ id} .volume span`);
|
||||
const rect = document.querySelector(`#audioEmbed-${ id} .selectableTrack`).getBoundingClientRect();
|
||||
|
||||
const protData = {
|
||||
"org.w3.clearkey": {
|
||||
"clearkeys": keys
|
||||
}
|
||||
};
|
||||
|
||||
const player = dashjs.MediaPlayer().create();
|
||||
player.initialize(audio, url, false);
|
||||
player.setProtectionData(protData);
|
||||
|
||||
playButton.on("click", () => {
|
||||
if (audio.paused) {
|
||||
document.querySelectorAll('audio').forEach(el => el.pause());
|
||||
audio.play();
|
||||
} else {
|
||||
audio.pause();
|
||||
}
|
||||
});
|
||||
|
||||
u(audio).on("timeupdate", () => {
|
||||
const time = audio.currentTime;
|
||||
const ps = Math.ceil((time * 100) / length);
|
||||
volumeSpan.html(fmtTime(Math.floor(time)));
|
||||
if (ps <= 100)
|
||||
trackDiv.nodes[0].style.width = `${ ps}%`;
|
||||
});
|
||||
|
||||
const playButtonImageUpdate = () => {
|
||||
if ($(`#audioEmbed-${ id} .claimed`).length === 0) {
|
||||
console.log(id);
|
||||
const imgSrc = audio.paused ? "/assets/packages/static/openvk/img/play.jpg" : "/assets/packages/static/openvk/img/pause.jpg";
|
||||
playButton.attr("src", imgSrc);
|
||||
}
|
||||
|
||||
if (!audio.paused) {
|
||||
$.post(`/audio${ id}/listen`, {
|
||||
hash: {$csrfToken}
|
||||
});
|
||||
}
|
||||
|
||||
$(`#audioEmbed-${ id} .performer`).toggle()
|
||||
$(`#audioEmbed-${ id} .track`).toggle()
|
||||
};
|
||||
|
||||
u(audio).on("play", playButtonImageUpdate);
|
||||
u(audio).on(["pause", "ended", "suspended"], playButtonImageUpdate);
|
||||
|
||||
u(`#audioEmbed-${ id} .track > div`).on("click", (e) => {
|
||||
let rect = document.querySelector("#audioEmbed-" + id + " .selectableTrack").getBoundingClientRect();
|
||||
const width = e.clientX - rect.left;
|
||||
const time = Math.ceil((width * length) / (rect.right - rect.left));
|
||||
console.log(width, length, rect.right, rect.left, time);
|
||||
audio.currentTime = time;
|
||||
});
|
||||
}
|
||||
|
||||
function addAudio(id) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: `/audio${ id}/action?act=add`,
|
||||
success: (response) => {
|
||||
if (response.success) {
|
||||
NewNotification("Успех", "Аудиозапись добавлена", "/assets/packages/static/openvk/img/oxygen-icons/64x64/actions/dialog-ok.png");
|
||||
} else {
|
||||
NewNotification("Ошибка", (response?.error ?? "Неизвестная ошибка"), "/assets/packages/static/openvk/img/error.png");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function removeAudio(id) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: `/audio${ id}/action?act=remove`,
|
||||
success: (response) => {
|
||||
if (response.success) {
|
||||
$(`#audioEmbed-${ id}`).remove();
|
||||
NewNotification("Успех", "Аудиозапись удалена", "/assets/packages/static/openvk/img/oxygen-icons/64x64/actions/dialog-ok.png");
|
||||
} else {
|
||||
NewNotification("Ошибка", (response?.error ?? "Неизвестная ошибка"), "/assets/packages/static/openvk/img/error.png");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function editAudio(id, title, performer, genre, lyrics) {
|
||||
$("#editAudioDialogBoxHtml input[name=name]").attr("v", title);
|
||||
$("#editAudioDialogBoxHtml input[name=performer]").attr("v", performer);
|
||||
$("#editAudioDialogBoxHtml select[name=genre]").attr("v", genre);
|
||||
$("#editAudioDialogBoxHtml textarea[name=lyrics]").attr("v", lyrics);
|
||||
|
||||
MessageBox({_edit}, $("#editAudioDialogBoxHtml").html(), [{_ok}, {_cancel}], [
|
||||
function() {
|
||||
let name = $(".ovk-diag-body input[name=name]").val();
|
||||
let perf = $(".ovk-diag-body input[name=performer]").val();
|
||||
let genre = $(".ovk-diag-body select[name=genre]").val();
|
||||
let lyrics = $(".ovk-diag-body textarea[name=lyrics]").val();
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: `/audio${ id}/action?act=edit`,
|
||||
data: {
|
||||
name: name,
|
||||
performer: performer,
|
||||
genre: genre,
|
||||
lyrics: lyrics,
|
||||
hash: {=$csrfToken}
|
||||
},
|
||||
success: (response) => {
|
||||
if (response.success) {
|
||||
NewNotification("Успех", "Аудиозапись отредактирована", "/assets/packages/static/openvk/img/oxygen-icons/64x64/actions/dialog-ok.png");
|
||||
setTimeout(() => { window.location.reload() }, 500)
|
||||
} else {
|
||||
NewNotification("Ошибка", (response?.error ?? "Неизвестная ошибка"), "/assets/packages/static/openvk/img/error.png");
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
Function.noop
|
||||
]);
|
||||
|
||||
$('.ovk-diag-body input, textarea, select').each(function() { $(this).val($(this).attr("v")); });
|
||||
}
|
||||
|
||||
{foreach $audios as $audio}
|
||||
initPlayer({$audio->getId()}, {$audio->getKeys()}, {$audio->getURL()}, {$audio->getLength()});
|
||||
{/foreach}
|
||||
</script>
|
|
@ -1,35 +1,36 @@
|
|||
<div
|
||||
id="audioEmbed-{$audio->getId()}"
|
||||
{if $canAdd || $canRemove || $canEdit}
|
||||
onmouseenter="$('#audioEmbed-{$audio->getId()} .track-additional-info').css('display', 'none'); $('#audioEmbed-{$audio->getId()} .buttons').css('display', 'flex');"
|
||||
onmouseleave="$('#audioEmbed-{$audio->getId()} .track-additional-info').css('display', 'flex'); $('#audioEmbed-{$audio->getId()} .buttons').css('display', 'none')"
|
||||
{/if}
|
||||
>
|
||||
{* На одной странице может оказаться несколько одинаковых аудиозаписей, и если запустить
|
||||
одну, то начнут проигрываться все остальные. Поэтому тут мы добавляем рандомное число*}
|
||||
|
||||
{php $id = $audio->getId() . rand(0, 1000)}
|
||||
{php $isWithdrawn = $audio->isWithdrawn()}
|
||||
<div id="audioEmbed-{$id}" data-realid="{$audio->getId()}" data-genre="{$audio->getGenre()}" class="audioEmbed {if !$audio->isAvailable()}lagged{/if} {if $isWithdrawn}withdrawn{/if}" onmouseenter="!this.classList.contains('inited') ? initPlayer({$id}, {$audio->getKeys()}, {$audio->getURL()}, {$audio->getLength()}) : console.log('Player already inited.')">
|
||||
<audio class="audio" />
|
||||
|
||||
<div id="miniplayer" class="audioEntry" style="min-height: 55px;">
|
||||
<div id="miniplayer" class="audioEntry" style="min-height: 37px;">
|
||||
<div class="playerButton">
|
||||
<img src="/assets/packages/static/openvk/img/play.jpg" />
|
||||
<div class="playIcon"></div>
|
||||
</div>
|
||||
|
||||
<div class="status" style="margin-top: 6px;">
|
||||
<div class="mediaInfo" style="margin-bottom: -8px; cursor: pointer;" onClick="window.location.href = '/audios/search?q=' + {$audio->getTitle()}"">
|
||||
<strong>
|
||||
{$audio->getTitle()}
|
||||
<div class="status" style="margin-top: 9px;">
|
||||
<div class="mediaInfo" style="margin-bottom: -8px; cursor: pointer;">
|
||||
<strong class="performer">
|
||||
<a style="color: unset !important;" href="/search?query=&type=audios&sort=id&only_performers=on&query={$audio->getPerformer()}">{ovk_proc_strtr($audio->getPerformer(), 50)}</a>
|
||||
</strong>
|
||||
</div>
|
||||
|
||||
<div class="performer" style="cursor: pointer;" onClick="window.location.href = '/audios/search?q=' + {$audio->getPerformer()} + '&by_performer=on'">
|
||||
<span class="nobold">
|
||||
{$audio->getPerformer()}
|
||||
—
|
||||
<span class="title {if !empty($audio->getLyrics())}withLyrics{/if}">
|
||||
{ovk_proc_strtr($audio->getTitle(), 50)}
|
||||
</span>
|
||||
<svg n:if="$audio->isExplicit()" xmlns="http://www.w3.org/2000/svg" height="11" viewBox="0 0 11 11" width="11">
|
||||
<path d="m1 2.506v5.988a1.5 1.5 0 0 0 1.491 1.506h6.019c.827 0 1.49-.674 1.49-1.506v-5.988a1.5 1.5 0 0 0 -1.491-1.506h-6.019c-.827 0-1.49.674-1.49 1.506zm4 2.494v-1h2v-1h-3v5h3v-1h-2v-1h2v-1zm-5-2.494a2.496 2.496 0 0 1 2.491-2.506h6.019a2.5 2.5 0 0 1 2.49 2.506v5.988a2.496 2.496 0 0 1 -2.491 2.506h-6.019a2.5 2.5 0 0 1 -2.49-2.506z"
|
||||
fill="#828a99" fill-opacity=".7"/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="track">
|
||||
<center class="claimed" style="width: 100%; padding: 4px; color: #45688E; font-weight: bold;" n:if="$audio->isWithdrawn()">
|
||||
{_audio_embed_withdrawn}
|
||||
<div class="track" style="margin-top: 3px">
|
||||
<center class="claimed" style="width: 100%; padding: 4px; color: #45688E; font-weight: bold;" n:if="$isWithdrawn">
|
||||
{_audio_embed_withdrawn}
|
||||
</center>
|
||||
<div class="selectableTrack" n:attr="style => $audio->isWithdrawn() ? 'display: none;' : 'border: #707070 1px solid;'">
|
||||
<div class="selectableTrack" n:attr="style => $isWithdrawn ? 'display: none;' : ''">
|
||||
<div>
|
||||
<!-- actual track -->
|
||||
</div>
|
||||
|
@ -37,21 +38,20 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="volume" style="display: flex; flex-direction: column;">
|
||||
<span class="nobold" style="text-align: right;">
|
||||
<div class="volume" style="display: flex; flex-direction: column;width:14%;">
|
||||
<span class="nobold" style="text-align: center;margin-top: 12px;">
|
||||
{$audio->getFormattedLength()}
|
||||
</span>
|
||||
<div class="track-additional-info" style="margin-top: 8px; align-self: flex-end;">
|
||||
<svg n:if="$audio->isExplicit()" xmlns="http://www.w3.org/2000/svg" height="11" viewBox="0 0 11 11" width="11">
|
||||
<path d="m1 2.506v5.988a1.5 1.5 0 0 0 1.491 1.506h6.019c.827 0 1.49-.674 1.49-1.506v-5.988a1.5 1.5 0 0 0 -1.491-1.506h-6.019c-.827 0-1.49.674-1.49 1.506zm4 2.494v-1h2v-1h-3v5h3v-1h-2v-1h2v-1zm-5-2.494a2.496 2.496 0 0 1 2.491-2.506h6.019a2.5 2.5 0 0 1 2.49 2.506v5.988a2.496 2.496 0 0 1 -2.491 2.506h-6.019a2.5 2.5 0 0 1 -2.49-2.506z"
|
||||
fill="#828a99" fill-opacity=".7"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="buttons" style="margin-top: 8px; display: none; justify-content: space-between; gap: 8px; align-self: flex-end;">
|
||||
<div n:if="$canAdd ?? true" class="icon add-icon" n:attr="onClick => $addOnClick ?? ''" />
|
||||
<div n:if="$canRemove" class="icon delete-icon" n:attr="onClick => $deleteOnClick ?? ''" />
|
||||
<div n:if="$canEdit" class="icon edit-icon" n:attr="onClick => $editOnClick ?? ''" />
|
||||
<div class="buttons" style="margin-top: 8px;">
|
||||
{php $hasAudio = $audio->isInLibraryOf($thisUser)}
|
||||
<div class="remove-icon musicIcon" data-id="{$audio->getId()}" n:if="$hasAudio" ></div>
|
||||
<div class="add-icon musicIcon" data-id="{$audio->getId()}" n:if="!$hasAudio" ></div>
|
||||
<div class="edit-icon musicIcon" data-lyrics="{$audio->getLyrics()}" data-title="{$audio->getTitle()}" data-performer="{$audio->getPerformer()}" n:if="$audio->canBeModifiedBy($thisUser) && !$isWithdrawn" ></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="lyrics" n:if="!empty($audio->getLyrics())">
|
||||
{nl2br($audio->getLyrics())|noescape}
|
||||
</div>
|
||||
</div>
|
|
@ -1,73 +1,10 @@
|
|||
<style>
|
||||
.icon {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: url("/assets/packages/static/openvk/img/common.png");
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.delete-icon { background-position: 0 -28.5px; }
|
||||
.edit-icon { background-position: 0 -44px; }
|
||||
.plus-icon { background-position: 0 0; }
|
||||
.save-icon { background-position: 0 -15.5px; }
|
||||
.list-icon { background-position: 0 -92.5px; }
|
||||
</style>
|
||||
|
||||
<div class="tabs">
|
||||
<div class="tab" n:attr="id => $mode === 'list' ? 'activetabs' : 'ki'">
|
||||
<a href="/audios{$thisUser->getId()}" n:attr="id => $mode === 'list' ? 'act_tab_a' : 'ki'">{$listText ?? "Моя музыка"}</a>
|
||||
</div>
|
||||
<div class="tab" n:attr="id => $mode === 'new' ? 'activetabs' : 'ki'">
|
||||
<a href="/audios/new" n:attr="id => $mode === 'new' ? 'act_tab_a' : 'ki'">Новое</a>
|
||||
</div>
|
||||
<div class="tab" n:attr="id => $mode === 'popular' ? 'activetabs' : 'ki'">
|
||||
<a href="/audios/popular" n:attr="id => $mode === 'popular' ? 'act_tab_a' : 'ki'">Популярное</a>
|
||||
</div>
|
||||
<div class="tab" n:attr="id => $mode === 'search' ? 'activetabs' : 'ki'">
|
||||
<a href="/audios/search" n:attr="id => $mode === 'search' ? 'act_tab_a' : 'ki'">Поиск</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="editAudioDialogBoxHtml" style="display: none;">
|
||||
<table cellspacing="7" cellpadding="0" border="0" align="center">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="120" valign="top">
|
||||
<span class="nobold">Имя:</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="name" autocomplete="off"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="120" valign="top">
|
||||
<span class="nobold">Исполнитель:</span>
|
||||
</td>
|
||||
<td>
|
||||
<input name="performer" autocomplete="off"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="120" valign="top">
|
||||
<span class="nobold">Жанр:</span>
|
||||
</td>
|
||||
<td>
|
||||
<select name="genre">
|
||||
<option n:foreach='\openvk\Web\Models\Entities\Audio::genres as $genre'
|
||||
n:attr="selected: $genre == 'Other'" value="{$genre}">
|
||||
{$genre}
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="120" valign="top">
|
||||
<span class="nobold">Текст:</span>
|
||||
</td>
|
||||
<td>
|
||||
<textarea name="lyrics"></textarea>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="searchOptions newer">
|
||||
<ul class="searchList">
|
||||
<a n:attr="id => $mode === 'list' ? 'used' : 'ki'" href="/audios{$thisUser->getId()}">{_my_music}</a>
|
||||
<a href="/player/upload">{_upload_audio}</a>
|
||||
<a n:attr="id => $mode === 'playlists' ? 'used' : 'ki'" href="/playlists{$thisUser->getId()}">{_my_playlists}</a>
|
||||
<a n:attr="id => $mode === 'new' ? 'used' : 'ki'" href="/audios/new">{_audio_new}</a>
|
||||
<a n:attr="id => $mode === 'popular' ? 'used' : 'ki'" href="/audios/popular">{_audio_popular}</a>
|
||||
<a href="/search?type=audios">{_audio_search}</a>
|
||||
</ul>
|
||||
</div>
|
|
@ -204,12 +204,14 @@
|
|||
</div>
|
||||
{elseif $type == "videos"}
|
||||
{foreach $data as $dat}
|
||||
<div class="content">
|
||||
{include "../components/video.xml", video => $dat}
|
||||
</div>
|
||||
<div class="content">
|
||||
{include "../components/video.xml", video => $dat}
|
||||
</div>
|
||||
{/foreach}
|
||||
{elseif $type == "audios"}
|
||||
хуй
|
||||
{foreach $data as $dat}
|
||||
{include "../Audio/player.xml", audio => $dat}
|
||||
{/foreach}
|
||||
{/if}
|
||||
{else}
|
||||
{ifset customErrorMessage}
|
||||
|
@ -230,12 +232,13 @@
|
|||
{block searchOptions}
|
||||
<div class="searchOptions">
|
||||
<ul class="searchList">
|
||||
<li {if $type === "users"} id="used"{/if}><a href="/search?type=users{if $_GET['query']}&query={urlencode($_GET['query'])}{/if}&sort=id"> {_s_people} {if $type === "users"} ({$count}){/if}</a></li>
|
||||
<li {if $type === "groups"} id="used"{/if}><a href="/search?type=groups{if $_GET['query']}&query={urlencode($_GET['query'])}{/if}&sort=id"> {_s_groups} {if $type === "groups"} ({$count}){/if}</a></li>
|
||||
<li {if $type === "comments"}id="used"{/if}><a href="/search?type=comments{if $_GET['query']}&query={urlencode($_GET['query'])}{/if}&sort=id">{_s_comments} {if $type === "comments"}({$count}){/if}</a></li>
|
||||
<li {if $type === "posts"} id="used"{/if}><a href="/search?type=posts{if $_GET['query']}&query={urlencode($_GET['query'])}{/if}&sort=id"> {_s_posts} {if $type === "posts"} ({$count}){/if}</a></li>
|
||||
<li {if $type === "videos"} id="used"{/if}><a href="/search?type=videos{if $_GET['query']}&query={urlencode($_GET['query'])}{/if}&sort=id"> {_s_videos} {if $type === "videos"} ({$count}){/if}</a></li>
|
||||
<li {if $type === "apps"} id="used"{/if}><a href="/search?type=apps{if $_GET['query']}&query={urlencode($_GET['query'])}{/if}&sort=id"> {_s_apps} {if $type === "apps"} ({$count}){/if}</a></li>
|
||||
<a {if $type === "users"} id="used"{/if} href="/search?type=users{if $_GET['query']}&query={urlencode($_GET['query'])}{/if}&sort=id"> {_s_people} {if $type === "users"} ({$count}){/if}</a>
|
||||
<a {if $type === "groups"} id="used"{/if} href="/search?type=groups{if $_GET['query']}&query={urlencode($_GET['query'])}{/if}&sort=id"> {_s_groups} {if $type === "groups"} ({$count}){/if}</a>
|
||||
<a {if $type === "comments"}id="used"{/if} href="/search?type=comments{if $_GET['query']}&query={urlencode($_GET['query'])}{/if}&sort=id">{_s_comments} {if $type === "comments"}({$count}){/if}</a>
|
||||
<a {if $type === "posts"} id="used"{/if} href="/search?type=posts{if $_GET['query']}&query={urlencode($_GET['query'])}{/if}&sort=id"> {_s_posts} {if $type === "posts"} ({$count}){/if}</a>
|
||||
<a {if $type === "videos"} id="used"{/if} href="/search?type=videos{if $_GET['query']}&query={urlencode($_GET['query'])}{/if}&sort=id"> {_s_videos} {if $type === "videos"} ({$count}){/if}</a>
|
||||
<a {if $type === "apps"} id="used"{/if} href="/search?type=apps{if $_GET['query']}&query={urlencode($_GET['query'])}{/if}&sort=id"> {_s_apps} {if $type === "apps"} ({$count}){/if}</a>
|
||||
<a {if $type === "audios"} id="used"{/if} href="/search?type=audios{if $_GET['query']}&query={urlencode($_GET['query'])}{/if}&sort=id"> {_s_audios} {if $type === "audios"} ({$count}){/if}</a>
|
||||
</ul>
|
||||
|
||||
<div class="searchOption">
|
||||
|
@ -249,6 +252,11 @@
|
|||
<option value="rating" {if $_GET["sort"] == "rating"}selected{/if}>{_s_order_by_rating}</option>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{if $type == "audios"}
|
||||
<option value="length" n:attr="selected => $_GET['sort'] == 'length'">{_s_order_by_length}</option>
|
||||
<option value="listens" n:attr="selected => $_GET['sort'] == 'listens'">{_s_order_by_listens}</option>
|
||||
{/if}
|
||||
</select>
|
||||
<div id="invertor">
|
||||
<input type="checkbox" name="invert" value="1" form="searcher" {if !is_null($_GET['invert']) && $_GET['invert'] == "1"}checked{/if}>{_s_order_invert}
|
||||
|
@ -351,9 +359,19 @@
|
|||
<input type="text" value="{if !is_null($_GET['fav_quote'])}{$_GET['fav_quote']}{/if}" form="searcher" placeholder="{_favorite_quotes}" name="fav_quote">
|
||||
</div>
|
||||
</div>
|
||||
<!--<input name="with_photo" type="checkbox" {if !is_null($_GET['with_photo']) && $_GET['with_photo'] == "on"}checked{/if} form="searcher">{_s_with_photo}-->
|
||||
{/if}
|
||||
<input class="button" type="button" id="dnt" value="{_reset}" onclick="resetSearch()">
|
||||
|
||||
{if $type == "audios"}
|
||||
<div class="searchOption">
|
||||
<div class="searchOptionName" id="n_main_audio" onclick="hideParams('main_audio')"><img src="/assets/packages/static/openvk/img/hide.png" class="searchHide">{_s_main}</div>
|
||||
<div class="searchOptionBlock" id="s_main_audio">
|
||||
<label><input type="checkbox" name="only_performers" n:attr="checked => !empty($_GET['only_performers'])" form="searcher">{_s_only_performers}</label>
|
||||
<label><input type="checkbox" name="with_lyrics" n:attr="checked => !empty($_GET['with_lyrics'])" form="searcher">{_s_with_lyrics}</label>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<input class="button" type="button" id="dnt" value="{_reset}" onclick="resetSearch()">
|
||||
</div>
|
||||
|
||||
{/block}
|
|
@ -594,6 +594,25 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div n:if="$audiosCount > 0">
|
||||
<div class="content_title_expanded" onclick="hidePanel(this, {$audiosCount});">
|
||||
{_audios}
|
||||
</div>
|
||||
<div>
|
||||
<div class="content_subtitle">
|
||||
{tr("audios_count", $audiosCount)}
|
||||
<div style="float:right;">
|
||||
<a href="/audios{$user->getId()}">{_all_title}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content_list long">
|
||||
<div class="audio" n:foreach="$audios as $audio" style="width: 100%;">
|
||||
{include "../Audio/player.xml", audio => $audio}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div n:if="OPENVK_ROOT_CONF['openvk']['preferences']['commerce'] && ($giftCount = $user->getGiftCount()) > 0">
|
||||
<div class="content_title_expanded" onclick="hidePanel(this, {$giftCount});">
|
||||
{_gifts}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
{css "css/dialog.css"}
|
||||
{css "css/notifications.css"}
|
||||
{css "css/avataredit.css"}
|
||||
{css "css/audios.css"}
|
||||
|
||||
{if $isXmas}
|
||||
{css "css/xmas.css"}
|
||||
|
@ -27,6 +28,7 @@
|
|||
{css "css/dialog.css"}
|
||||
{css "css/notifications.css"}
|
||||
{css "css/avataredit.css"}
|
||||
{css "css/audios.css"}
|
||||
|
||||
{if $isXmas}
|
||||
{css "css/xmas.css"}
|
||||
|
|
|
@ -50,6 +50,10 @@
|
|||
{else}
|
||||
{include "post.xml", post => $attachment, compact => true}
|
||||
{/if}
|
||||
{elseif $attachment instanceof \openvk\Web\Models\Entities\Audio}
|
||||
<div style="width:100%;">
|
||||
{include "../Audio/player.xml", audio => $attachment}
|
||||
</div>
|
||||
{else}
|
||||
<span style="color:red;">{_version_incompatibility}</span>
|
||||
{/if}
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
</div>
|
||||
<div class="post-has-videos"></div>
|
||||
<div class="post-has-audio"></div>
|
||||
|
||||
<div n:if="$postOpts ?? true" class="post-opts">
|
||||
{var $anonEnabled = OPENVK_ROOT_CONF['openvk']['preferences']['wall']['anonymousPosting']['enable']}
|
||||
|
@ -89,6 +90,10 @@
|
|||
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/mimetypes/application-x-srt.png" />
|
||||
{_note}
|
||||
</a>
|
||||
<a id="_audioAttachment">
|
||||
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/mimetypes/audio-ac3.png" />
|
||||
{_audio}
|
||||
</a>
|
||||
<a n:if="$graffiti ?? false" href="javascript:initGraffiti({$textAreaId});">
|
||||
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/actions/draw-brush.png" />
|
||||
{_graffiti}
|
||||
|
|
|
@ -209,6 +209,8 @@ routes:
|
|||
handler: "Audio->newPlaylist"
|
||||
- url: "/playlist{num}_{num}"
|
||||
handler: "Audio->playlist"
|
||||
- url: "/playlists{num}"
|
||||
handler: "Audio->playlists"
|
||||
- url: "/audio{num}/action"
|
||||
handler: "Audio->action"
|
||||
- url: "/{?!club}{num}"
|
||||
|
|
322
Web/static/css/audios.css
Normal file
322
Web/static/css/audios.css
Normal file
|
@ -0,0 +1,322 @@
|
|||
.musicIcon {
|
||||
background-image: url('/assets/packages/static/openvk/img/audios_controls.png');
|
||||
background-repeat: no-repeat;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.bigPlayer {
|
||||
background-color: rgb(240, 241, 242);
|
||||
margin-left: -10px;
|
||||
margin-top: -10px;
|
||||
width: 102.8%;
|
||||
height: 46px;
|
||||
border-bottom: 1px solid #d8d8d8;
|
||||
box-shadow: 1px 0px 8px 0px rgba(34, 60, 80, 0.2);
|
||||
}
|
||||
|
||||
.bigPlayer .paddingLayer {
|
||||
padding: 0px 0px 0px 14px;
|
||||
}
|
||||
|
||||
.bigPlayer .paddingLayer .playButtons {
|
||||
padding: 12px 0px;
|
||||
}
|
||||
|
||||
.bigPlayer .paddingLayer .playButtons .playButton {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.bigPlayer .paddingLayer .playButtons .nextButton {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-position-y: -47px;
|
||||
}
|
||||
|
||||
.bigPlayer .paddingLayer .playButtons .backButton {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-position-y: -47px;
|
||||
background-position-x: -16px;
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.bigPlayer .paddingLayer .additionalButtons {
|
||||
float: left;
|
||||
margin-top: -6px;
|
||||
width: 12%;
|
||||
}
|
||||
|
||||
.bigPlayer .paddingLayer .additionalButtons .repeatButton {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-position-y: -49px;
|
||||
background-position-x: -31px;
|
||||
margin-left: 6px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.bigPlayer .paddingLayer .additionalButtons .shuffleButton {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-position: -50px -50px;
|
||||
margin-left: 6px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.bigPlayer .paddingLayer .additionalButtons .deviceButton {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-position: -202px -51px;
|
||||
margin-left: 6px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.bigPlayer .paddingLayer .playButtons .arrowsButtons {
|
||||
/* скибиди пидорас */
|
||||
float: left;
|
||||
display: flex;
|
||||
padding-left: 4px;
|
||||
padding-top: 1.2px;
|
||||
}
|
||||
|
||||
.bigPlayer .paddingLayer .trackPanel {
|
||||
float: left;
|
||||
margin-top: -13px;
|
||||
margin-left: 13px;
|
||||
width: 386px;
|
||||
}
|
||||
|
||||
.bigPlayer .paddingLayer .volumePanel {
|
||||
width: 73px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.bigPlayer .paddingLayer .slider {
|
||||
width: 18px;
|
||||
height: 7px;
|
||||
background: #707070;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
top: 0px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.bigPlayer .paddingLayer .trackInfo .time {
|
||||
float: right;
|
||||
font-size: 10px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.music-app {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.music-app--player {
|
||||
display: grid;
|
||||
grid-template-columns: 32px 32px 32px 1fr;
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid #c1c1c1;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-style: dashed;
|
||||
}
|
||||
|
||||
.music-app--player .play,
|
||||
.music-app--player .perv,
|
||||
.music-app--player .next {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
background-color: #507597;
|
||||
color: #fff;
|
||||
height: 20px;
|
||||
margin: 5px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.music-app--player .info {
|
||||
margin-left: 10px;
|
||||
width: 550px;
|
||||
}
|
||||
|
||||
.music-app--player .info .song-name * {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.music-app--player .info .song-name time {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.music-app--player .info .track {
|
||||
margin-top: 8px;
|
||||
height: 5px;
|
||||
width: 70%;
|
||||
background-color: #fff;
|
||||
border-top: 1px solid #507597;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.music-app--player .info .track .inner-track {
|
||||
background-color: #507597;
|
||||
height: inherit;
|
||||
width: 15px;
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.audioEntry .status .track > .selectableTrack, .bigPlayer .selectableTrack {
|
||||
margin-top: 3px;
|
||||
width: calc(100% - 8px);
|
||||
border-top: #707070 1px solid;
|
||||
height: 6px;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.audioEntry .status .track > div > div {
|
||||
height: 100%;
|
||||
width: 0%;
|
||||
background-color: #BFBFBF;
|
||||
}
|
||||
|
||||
#audioEmbed {
|
||||
user-select: none;
|
||||
background: #eee;
|
||||
height: 40px;
|
||||
width: 486px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-right: -50%;
|
||||
transform: translate(-50%, -50%);
|
||||
overflow: hidden;
|
||||
border: 1px solid #8B8B8B;
|
||||
}
|
||||
|
||||
.audioEntry {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.audioEntry .playerButton {
|
||||
position: relative;
|
||||
padding: 9px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.audioEntry .playerButton .playIcon {
|
||||
background-image: url('/assets/packages/static/openvk/img/play_buttons.gif');
|
||||
cursor: pointer;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.audioEntry .playerButton .playIcon.paused {
|
||||
background-position-y: -16px;
|
||||
}
|
||||
|
||||
.audioEntry .status {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
width: 85%;
|
||||
height: 23px;
|
||||
}
|
||||
|
||||
.audioEntry .status strong {
|
||||
color: #4C4C4C;
|
||||
}
|
||||
|
||||
.audioEntry .status .track {
|
||||
display: none;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
#audioEmbed .audioEntry .status .track, .audioEntry.playing .status .track {
|
||||
display: unset;
|
||||
}
|
||||
|
||||
.audioEntry:hover {
|
||||
background: #EEF0F2;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.audioEntry:hover .buttons {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.audioEntry .buttons {
|
||||
display: none;
|
||||
width: 62px;
|
||||
height: 20px;
|
||||
position: absolute;
|
||||
right: 46px;
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
.audioEntry .buttons .edit-icon {
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
float: right;
|
||||
margin-right: 4px;
|
||||
margin-top: 3px;
|
||||
background-position: -137px -51px;
|
||||
}
|
||||
|
||||
.audioEntry .buttons .add-icon {
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
float: right;
|
||||
background-position: -80px -52px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.add-icon-noaction {
|
||||
background-image: url('/assets/packages/static/openvk/img/audios_controls.png');
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
float: right;
|
||||
background-position: -94px -52px;
|
||||
margin-top: 2px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.audioEntry .buttons .remove-icon {
|
||||
margin-top: 3px;
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
float: right;
|
||||
background-position: -108px -52px;
|
||||
}
|
||||
|
||||
.audioEmbed .lyrics {
|
||||
display: none;
|
||||
padding: 0px 33px;
|
||||
}
|
||||
|
||||
.audioEmbed .lyrics.showed {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.audioEntry .withLyrics {
|
||||
user-select: none;
|
||||
color: #507597;
|
||||
}
|
||||
|
||||
.audioEntry .withLyrics:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.audioEmbed.withdrawn .status > *, .audioEmbed.withdrawn .playerButton > * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.audioEmbed.withdrawn {
|
||||
filter: opacity(0.8);
|
||||
}
|
|
@ -1242,64 +1242,6 @@ table.User {
|
|||
border-bottom: 1px solid #c3cad2;
|
||||
}
|
||||
|
||||
.music-app {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.music-app--player {
|
||||
display: grid;
|
||||
grid-template-columns: 32px 32px 32px 1fr;
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid #c1c1c1;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-style: dashed;
|
||||
}
|
||||
|
||||
.music-app--player .play,
|
||||
.music-app--player .perv,
|
||||
.music-app--player .next {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
background-color: #507597;
|
||||
color: #fff;
|
||||
height: 20px;
|
||||
margin: 5px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.music-app--player .info {
|
||||
margin-left: 10px;
|
||||
width: 550px;
|
||||
}
|
||||
|
||||
.music-app--player .info .song-name * {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.music-app--player .info .song-name time {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.music-app--player .info .track {
|
||||
margin-top: 8px;
|
||||
height: 5px;
|
||||
width: 70%;
|
||||
background-color: #fff;
|
||||
border-top: 1px solid #507597;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.music-app--player .info .track .inner-track {
|
||||
background-color: #507597;
|
||||
height: inherit;
|
||||
width: 15px;
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.settings_delete {
|
||||
margin: -12px;
|
||||
padding: 12px;
|
||||
|
@ -2161,77 +2103,6 @@ li {
|
|||
padding-left: 15px;
|
||||
}
|
||||
|
||||
#audioEmbed {
|
||||
user-select: none;
|
||||
background: #eee;
|
||||
height: 40px;
|
||||
width: 486px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-right: -50%;
|
||||
transform: translate(-50%, -50%);
|
||||
overflow: hidden;
|
||||
border: 1px solid #8B8B8B;
|
||||
}
|
||||
|
||||
.audioEntry {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 10fr 1fr;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.audioEntry > * {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.audioEntry .playerButton {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.audioEntry .playerButton img {
|
||||
position: absolute;
|
||||
max-width: 50%;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-right: -50%;
|
||||
transform: translate(-50%, -50%);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.audioEntry .status {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.audioEntry .status strong {
|
||||
color: #4C4C4C;
|
||||
}
|
||||
|
||||
.audioEntry .status .track {
|
||||
display: none;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
#audioEmbed .audioEntry .status .track, .audioEntry.playing .status .track {
|
||||
display: unset;
|
||||
}
|
||||
|
||||
.audioEntry .status .track > .selectableTrack {
|
||||
margin-top: 3px;
|
||||
width: calc(100% - 8px);
|
||||
border-top: #707070 1px solid;
|
||||
height: 6px;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.audioEntry .status .track > div > div {
|
||||
height: 100%;
|
||||
width: 0%;
|
||||
background-color: #BFBFBF;
|
||||
}
|
||||
|
||||
#votesBalance {
|
||||
margin-top: 10px;
|
||||
padding: 7px;
|
||||
|
@ -2582,8 +2453,7 @@ a.poll-retract-vote {
|
|||
display: none;
|
||||
}
|
||||
|
||||
.searchOptions
|
||||
{
|
||||
.searchOptions {
|
||||
overflow: hidden;
|
||||
width:25.5%;
|
||||
border-top:1px solid #E5E7E6;
|
||||
|
@ -2594,8 +2464,7 @@ a.poll-retract-vote {
|
|||
margin-right: -7px;
|
||||
}
|
||||
|
||||
.searchBtn
|
||||
{
|
||||
.searchBtn {
|
||||
border: solid 1px #575757;
|
||||
background-color: #696969;
|
||||
color:white;
|
||||
|
@ -2607,25 +2476,22 @@ a.poll-retract-vote {
|
|||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.searchBtn:active
|
||||
{
|
||||
.searchBtn:active {
|
||||
border: solid 1px #666666;
|
||||
background-color: #696969;
|
||||
color:white;
|
||||
box-shadow: 0px -2px 0px 0px rgba(255, 255, 255, 0.18) inset;
|
||||
}
|
||||
|
||||
.searchList
|
||||
{
|
||||
.searchList {
|
||||
list-style: none;
|
||||
user-select: none;
|
||||
padding-left:0px;
|
||||
}
|
||||
|
||||
.searchList #used
|
||||
{
|
||||
.searchList #used {
|
||||
margin-left:0px;
|
||||
color: white;
|
||||
color: white !important;
|
||||
padding:2px;
|
||||
padding-top:5px;
|
||||
padding-bottom:5px;
|
||||
|
@ -2636,23 +2502,21 @@ a.poll-retract-vote {
|
|||
width:87%;
|
||||
}
|
||||
|
||||
.searchList #used a
|
||||
{
|
||||
.searchList #used a {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.sr:focus
|
||||
{
|
||||
.sr:focus {
|
||||
outline:none;
|
||||
}
|
||||
|
||||
.searchHide
|
||||
{
|
||||
.searchHide {
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.searchList li
|
||||
.searchList li, .searchList a
|
||||
{
|
||||
display: block;
|
||||
margin-left:0px;
|
||||
color: #2B587A !important;
|
||||
cursor:pointer;
|
||||
|
@ -2663,13 +2527,15 @@ a.poll-retract-vote {
|
|||
padding-left:9px;
|
||||
}
|
||||
|
||||
.searchList li a
|
||||
{
|
||||
.searchList li a {
|
||||
min-width:100%;
|
||||
}
|
||||
|
||||
.searchList li:hover
|
||||
{
|
||||
.searchList a {
|
||||
min-width: 88%;
|
||||
}
|
||||
|
||||
.searchList li:hover, .searchList a:hover {
|
||||
margin-left:0px;
|
||||
color: #2B587A !important;
|
||||
background:#ebebeb;
|
||||
|
@ -2681,8 +2547,7 @@ a.poll-retract-vote {
|
|||
width:91%;
|
||||
}
|
||||
|
||||
.whatFind
|
||||
{
|
||||
.whatFind {
|
||||
color:rgb(128, 128, 128);
|
||||
background:none;
|
||||
border:none;
|
||||
|
@ -3084,3 +2949,10 @@ body.article .floating_sidebar, body.article .page_content {
|
|||
background: #E9F0F1 !important;
|
||||
}
|
||||
|
||||
.searchOptions.newer {
|
||||
padding-left: 6px;
|
||||
border-top: unset !important;
|
||||
height: unset !important;
|
||||
border-left: 1px solid #d8d8d8;
|
||||
width: 26% !important;
|
||||
}
|
||||
|
|
BIN
Web/static/img/audios_controls.png
Normal file
BIN
Web/static/img/audios_controls.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
BIN
Web/static/img/play_buttons.gif
Normal file
BIN
Web/static/img/play_buttons.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 110 B |
250
Web/static/js/al_music.js
Normal file
250
Web/static/js/al_music.js
Normal file
|
@ -0,0 +1,250 @@
|
|||
function fmtTime(time) {
|
||||
const mins = String(Math.floor(time / 60)).padStart(2, '0');
|
||||
const secs = String(Math.floor(time % 60)).padStart(2, '0');
|
||||
return `${ mins}:${ secs}`;
|
||||
}
|
||||
|
||||
function initPlayer(id, keys, url, length) {
|
||||
document.querySelector(`#audioEmbed-${ id}`).classList.add("inited")
|
||||
const audio = document.querySelector(`#audioEmbed-${ id} .audio`);
|
||||
const playButton = u(`#audioEmbed-${ id} .playerButton > .playIcon`);
|
||||
const trackDiv = u(`#audioEmbed-${ id} .track > div > div`);
|
||||
const volumeSpan = u(`#audioEmbed-${ id} .volume span`);
|
||||
const rect = document.querySelector(`#audioEmbed-${ id} .selectableTrack`).getBoundingClientRect();
|
||||
|
||||
const protData = {
|
||||
"org.w3.clearkey": {
|
||||
"clearkeys": keys
|
||||
}
|
||||
};
|
||||
|
||||
const player = dashjs.MediaPlayer().create();
|
||||
player.initialize(audio, url, false);
|
||||
player.setProtectionData(protData);
|
||||
|
||||
playButton.on("click", () => {
|
||||
if (audio.paused) {
|
||||
document.querySelectorAll('audio').forEach(el => el.pause());
|
||||
audio.play();
|
||||
} else {
|
||||
audio.pause();
|
||||
}
|
||||
});
|
||||
|
||||
u(audio).on("timeupdate", () => {
|
||||
const time = audio.currentTime;
|
||||
const ps = Math.ceil((time * 100) / length);
|
||||
volumeSpan.html(fmtTime(Math.floor(time)));
|
||||
if (ps <= 100)
|
||||
trackDiv.nodes[0].style.width = `${ ps}%`;
|
||||
|
||||
if(audio.paused) {
|
||||
playButtonImageUpdate()
|
||||
}
|
||||
});
|
||||
|
||||
const playButtonImageUpdate = () => {
|
||||
if ($(`#audioEmbed-${ id} .claimed`).length === 0) {
|
||||
console.log(id);
|
||||
}
|
||||
|
||||
if (!audio.paused) {
|
||||
playButton.addClass("paused")
|
||||
$.post(`/audio${ id}/listen`, {
|
||||
hash: u("meta[name=csrf]").attr("value")
|
||||
});
|
||||
} else {
|
||||
playButton.removeClass("paused")
|
||||
}
|
||||
|
||||
if(!$(`#audioEmbed-${ id}`).hasClass("havePlayed")) {
|
||||
$(`#audioEmbed-${ id}`).addClass("havePlayed")
|
||||
|
||||
$(`#audioEmbed-${ id} .track`).toggle()
|
||||
}
|
||||
};
|
||||
|
||||
u(audio).on("play", playButtonImageUpdate);
|
||||
u(audio).on(["pause", "ended", "suspended"], playButtonImageUpdate);
|
||||
|
||||
u(`#audioEmbed-${ id} .track > div`).on("click", (e) => {
|
||||
let rect = document.querySelector("#audioEmbed-" + id + " .selectableTrack").getBoundingClientRect();
|
||||
const width = e.clientX - rect.left;
|
||||
const time = Math.ceil((width * length) / (rect.right - rect.left));
|
||||
console.log(width, length, rect.right, rect.left, time);
|
||||
audio.currentTime = time;
|
||||
});
|
||||
}
|
||||
|
||||
$(document).on("click", ".musicIcon.edit-icon", (e) => {
|
||||
let player = e.currentTarget.closest(".audioEmbed")
|
||||
let id = Number(player.dataset.realid)
|
||||
let performer = e.currentTarget.dataset.performer
|
||||
let name = e.currentTarget.dataset.title
|
||||
let genre = player.dataset.genre
|
||||
let lyrics = e.currentTarget.dataset.lyrics
|
||||
MessageBox(tr("edit"), `
|
||||
<div>
|
||||
${tr("audio_name")}
|
||||
<input name="name" maxlength="40" type="text" value="${name}">
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 11px">
|
||||
${tr("performer")}
|
||||
<input name="performer" maxlength="40" type="text" value="${performer}">
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 11px">
|
||||
${tr("genre")}
|
||||
<select name="genre"></select>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 11px">
|
||||
${tr("lyrics")}
|
||||
<textarea name="lyrics" maxlength="500">${lyrics ?? ""}</textarea>
|
||||
</div>
|
||||
`, [tr("ok"), tr("cancel")], [
|
||||
function() {
|
||||
let t_name = $(".ovk-diag-body input[name=name]").val();
|
||||
let t_perf = $(".ovk-diag-body input[name=performer]").val();
|
||||
let t_genre = $(".ovk-diag-body select[name=genre]").val();
|
||||
let t_lyrics = $(".ovk-diag-body textarea[name=lyrics]").val();
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: `/audio${id}/action?act=edit`,
|
||||
data: {
|
||||
name: t_name,
|
||||
performer: t_perf,
|
||||
genre: t_genre,
|
||||
lyrics: t_lyrics,
|
||||
hash: u("meta[name=csrf]").attr("value")
|
||||
},
|
||||
success: (response) => {
|
||||
if(response.success) {
|
||||
let perf = player.querySelector(".performer a")
|
||||
perf.innerHTML = response.new_info.performer
|
||||
perf.setAttribute("href", "/search?query=&type=audios&sort=id&only_performers=on&query="+response.new_info.performer)
|
||||
|
||||
e.currentTarget.setAttribute("data-performer", response.new_info.performer)
|
||||
let name = player.querySelector(".title")
|
||||
name.innerHTML = escapeHtml(response.new_info.name)
|
||||
|
||||
e.currentTarget.setAttribute("data-title", response.new_info.name)
|
||||
|
||||
if(player.querySelector(".lyrics") != null) {
|
||||
player.querySelector(".lyrics").innerHTML = response.new_info.lyrics
|
||||
player.querySelector(".title").classList.ad
|
||||
} else {
|
||||
player.insertAdjacentHTML("beforeend", `
|
||||
<div class="lyrics" n:if="!empty($audio->getLyrics())">
|
||||
${response.new_info.lyrics}
|
||||
</div>
|
||||
`)
|
||||
}
|
||||
|
||||
e.currentTarget.setAttribute("data-lyrics", response.new_info.lyrics_unformatted)
|
||||
player.setAttribute("data-genre", response.new_info.genre)
|
||||
console.log(response)
|
||||
} else {
|
||||
MessageBox(tr("error"), response.flash.message, [tr("ok")], [Function.noop])
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
Function.noop
|
||||
]);
|
||||
|
||||
window.openvk.audio_genres.forEach(elGenre => {
|
||||
document.querySelector(".ovk-diag-body select[name=genre]").insertAdjacentHTML("beforeend", `
|
||||
<option value="${elGenre}" ${elGenre == genre ? "selected" : ""}>${elGenre}</option>
|
||||
`)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
$(document).on("click", ".title.withLyrics", (e) => {
|
||||
let parent = e.currentTarget.closest(".audioEmbed")
|
||||
|
||||
parent.querySelector(".lyrics").classList.toggle("showed")
|
||||
})
|
||||
|
||||
$(document).on("click", ".musicIcon.remove-icon", (e) => {
|
||||
let id = e.currentTarget.dataset.id
|
||||
let formdata = new FormData()
|
||||
formdata.append("hash", u("meta[name=csrf]").attr("value"))
|
||||
|
||||
ky.post(`/audio${id}/action?act=remove`, {
|
||||
hooks: {
|
||||
beforeRequest: [
|
||||
(_request) => {
|
||||
e.currentTarget.classList.add("lagged")
|
||||
}
|
||||
],
|
||||
afterResponse: [
|
||||
async (_request, _options, response) => {
|
||||
let json = await response.json()
|
||||
|
||||
if(json.success) {
|
||||
e.currentTarget.classList.remove("remove-icon")
|
||||
e.currentTarget.classList.add("add-icon")
|
||||
e.currentTarget.classList.remove("lagged")
|
||||
|
||||
let withd = e.currentTarget.closest(".audioEmbed.withdrawn")
|
||||
if(withd != null) {
|
||||
u(withd).remove()
|
||||
}
|
||||
} else {
|
||||
MessageBox(tr("error"), json.flash.message, [tr("ok")], [Function.noop])
|
||||
}
|
||||
}
|
||||
]
|
||||
}, body: formdata
|
||||
})
|
||||
})
|
||||
|
||||
$(document).on("click", ".musicIcon.add-icon", (e) => {
|
||||
let id = e.currentTarget.dataset.id
|
||||
|
||||
let formdata = new FormData()
|
||||
formdata.append("hash", u("meta[name=csrf]").attr("value"))
|
||||
|
||||
ky.post(`/audio${id}/action?act=add`, {
|
||||
hooks: {
|
||||
beforeRequest: [
|
||||
(_request) => {
|
||||
e.currentTarget.classList.add("lagged")
|
||||
}
|
||||
],
|
||||
afterResponse: [
|
||||
async (_request, _options, response) => {
|
||||
let json = await response.json()
|
||||
|
||||
if(json.success) {
|
||||
e.currentTarget.classList.remove("add-icon")
|
||||
e.currentTarget.classList.add("remove-icon")
|
||||
e.currentTarget.classList.remove("lagged")
|
||||
} else {
|
||||
MessageBox(tr("error"), json.flash.message, [tr("ok")], [Function.noop])
|
||||
}
|
||||
}
|
||||
]
|
||||
}, body: formdata
|
||||
})
|
||||
})
|
||||
|
||||
$(document).on("click", "#_audioAttachment", (e) => {
|
||||
let body = `
|
||||
я ещё не сделал
|
||||
`
|
||||
MessageBox(tr("select_audio"), body, [tr("ok")], [Function.noop])
|
||||
})
|
||||
|
||||
$(document).on("click", ".audioEmbed.lagged", (e) => {
|
||||
MessageBox(tr("error"), tr("audio_embed_processing"), [tr("ok")], [Function.noop])
|
||||
})
|
||||
|
||||
$(document).on("click", ".audioEmbed.withdrawn", (e) => {
|
||||
MessageBox(tr("error"), tr("audio_embed_withdrawn"), [tr("ok")], [Function.noop])
|
||||
})
|
|
@ -479,6 +479,7 @@
|
|||
"my_videos" = "My Videos";
|
||||
"my_messages" = "My Messages";
|
||||
"my_notes" = "My Notes";
|
||||
"my_audios" = "My Audios";
|
||||
"my_groups" = "My Groups";
|
||||
"my_feed" = "My Feed";
|
||||
"my_feedback" = "My Feedback";
|
||||
|
@ -719,6 +720,56 @@
|
|||
"change_video" = "Change video";
|
||||
"unknown_video" = "This video is not supported in your version of OpenVK.";
|
||||
|
||||
|
||||
/* Audios */
|
||||
|
||||
"audios" = "Audios";
|
||||
"audio" = "Audio";
|
||||
"upload_audio" = "Upload audio";
|
||||
|
||||
"performer" = "Performer";
|
||||
"audio_name" = "Name";
|
||||
"genre" = "Genre";
|
||||
"lyrics" = "Lyrics";
|
||||
|
||||
"select_another_file" = "Select another file";
|
||||
|
||||
"limits" = "Limits";
|
||||
"select_audio" = "Select audio from your computer";
|
||||
"audio_requirements" = "Audio must be between $1s to $2 minutes, weights to $3MB, contain audio stream must not infringe on the copyright";
|
||||
"you_can_also_add_audio_using" = "You can also add audio from among the files you have already downloaded using";
|
||||
"search_audio_inst" = "audios search";
|
||||
|
||||
"audio_embed_not_found" = "Audio not found";
|
||||
"audio_embed_deleted" = "Audio was deleted";
|
||||
"audio_embed_withdrawn" = "The audio was withdrawn at the request of the copyright holder";
|
||||
"audio_embed_forbidden" = "The user's privacy settings do not allow this embed this audio";
|
||||
"audio_embed_processing" = "Audio is still being processed, or has not been processed correctly.";
|
||||
|
||||
"audios_count_zero" = "No audios";
|
||||
"audios_count_one" = "One audio";
|
||||
"audios_count_few" = "$1 audios";
|
||||
"audios_count_many" = "$1 audios";
|
||||
"audios_count_other" = "$1 audios";
|
||||
|
||||
"track_unknown" = "Unknown";
|
||||
"track_noname" = "Without name";
|
||||
|
||||
"my_music" = "My music";
|
||||
"audio_new" = "New";
|
||||
"audio_popular" = "Popular";
|
||||
"audio_search" = "Search";
|
||||
|
||||
"my_audios_small" = "My audios";
|
||||
"my_playlists" = "My playlists";
|
||||
"playlists" = "Playlists";
|
||||
"audios_explicit" = "Contains obscene language";
|
||||
"withdrawn" = "Withdrawn";
|
||||
"deleted" = "Deleted";
|
||||
"owner" = "Owner";
|
||||
|
||||
"select_audio" = "Select audios";
|
||||
|
||||
/* Notifications */
|
||||
|
||||
"feedback" = "Feedback";
|
||||
|
@ -1287,6 +1338,10 @@
|
|||
|
||||
"description_too_long" = "Description is too long.";
|
||||
|
||||
"invalid_audio" = "Invalid audio.";
|
||||
"do_not_have_audio" = "You don't have this audio.";
|
||||
"do_have_audio" = "You already have this audio.";
|
||||
|
||||
/* Admin actions */
|
||||
|
||||
"login_as" = "Login as $1";
|
||||
|
@ -1732,6 +1787,8 @@
|
|||
"s_order_by_name" = "By name";
|
||||
"s_order_by_random" = "By random";
|
||||
"s_order_by_rating" = "By rating";
|
||||
"s_order_by_length" = "By length";
|
||||
"s_order_by_listens" = "By listens count";
|
||||
"s_order_invert" = "Invert";
|
||||
|
||||
"s_by_date" = "By date";
|
||||
|
@ -1753,6 +1810,8 @@
|
|||
"deleted_target_comment" = "This comment belongs to deleted post";
|
||||
|
||||
"no_results" = "No results";
|
||||
"s_only_performers" = "Performers only";
|
||||
"s_with_lyrics" = "With lyrics";
|
||||
|
||||
/* BadBrowser */
|
||||
|
||||
|
|
|
@ -461,6 +461,7 @@
|
|||
"my_videos" = "Мои Видеозаписи";
|
||||
"my_messages" = "Мои Сообщения";
|
||||
"my_notes" = "Мои Заметки";
|
||||
"my_audios" = "Мои Аудиозаписи";
|
||||
"my_groups" = "Мои Группы";
|
||||
"my_feed" = "Мои Новости";
|
||||
"my_feedback" = "Мои Ответы";
|
||||
|
@ -682,10 +683,12 @@
|
|||
"upload_audio" = "Загрузить аудио";
|
||||
|
||||
"performer" = "Исполнитель";
|
||||
"audio_name" = "Имя композиции";
|
||||
"audio_name" = "Название";
|
||||
"genre" = "Жанр";
|
||||
"lyrics" = "Текст";
|
||||
|
||||
"select_another_file" = "Выбрать другой файл";
|
||||
|
||||
"limits" = "Ограничения";
|
||||
"select_audio" = "Выберите аудиозапись на Вашем компьютере";
|
||||
"audio_requirements" = "Аудиозапись должна быть длинной от $1c до $2 минут, весить до $3мб, содержать аудиопоток и не нарушать авторские права.";
|
||||
|
@ -696,7 +699,31 @@
|
|||
"audio_embed_deleted" = "Аудиозапись была удалена";
|
||||
"audio_embed_withdrawn" = "Аудиозапись была изъята по обращению правообладателя";
|
||||
"audio_embed_forbidden" = "Настройки приватности пользователя не позволяют встраивать эту композицию";
|
||||
"audio_embed_processing" = "Композиция ещё обрабатывается";
|
||||
"audio_embed_processing" = "Аудио ещё обрабатывается, либо обработалось неправильно.";
|
||||
|
||||
"audios_count_zero" = "Нет аудиозаписей";
|
||||
"audios_count_one" = "Одна аудиозапись";
|
||||
"audios_count_few" = "$1 аудиозаписи";
|
||||
"audios_count_many" = "$1 аудиозаписей";
|
||||
"audios_count_other" = "$1 аудиозаписей";
|
||||
|
||||
"track_unknown" = "Неизвестен";
|
||||
"track_noname" = "Без названия";
|
||||
|
||||
"my_music" = "Моя музыка";
|
||||
"audio_new" = "Новое";
|
||||
"audio_popular" = "Популярное";
|
||||
"audio_search" = "Поиск";
|
||||
|
||||
"my_audios_small" = "Мои аудиозаписи";
|
||||
"my_playlists" = "Мои плейлисты";
|
||||
"playlists" = "Плейлисты";
|
||||
"audios_explicit" = "Содержит нецензурную лексику";
|
||||
"withdrawn" = "Изъято";
|
||||
"deleted" = "Удалено";
|
||||
"owner" = "Владелец";
|
||||
|
||||
"select_audio" = "Выбрать аудиозаписи";
|
||||
|
||||
/* Notifications */
|
||||
|
||||
|
@ -1207,6 +1234,9 @@
|
|||
"group_owner_is_banned" = "Создатель сообщества успешно забанен.";
|
||||
"group_is_banned" = "Сообщество успешно забанено";
|
||||
"description_too_long" = "Описание слишком длинное.";
|
||||
"invalid_audio" = "Такой аудиозаписи не существует.";
|
||||
"do_not_have_audio" = "У вас нет этой аудиозаписи.";
|
||||
"do_have_audio" = "У вас уже есть эта аудиозапись.";
|
||||
|
||||
/* Admin actions */
|
||||
|
||||
|
@ -1646,6 +1676,8 @@
|
|||
"s_order_by_name" = "По имени";
|
||||
"s_order_by_random" = "По случайности";
|
||||
"s_order_by_rating" = "По рейтингу";
|
||||
"s_order_by_length" = "По длине";
|
||||
"s_order_by_listens" = "По числу прослушиваний";
|
||||
"s_order_invert" = "Инвертировать";
|
||||
|
||||
"s_by_date" = "По дате";
|
||||
|
@ -1667,6 +1699,8 @@
|
|||
"deleted_target_comment" = "Этот комментарий принадлежит к удалённой записи";
|
||||
|
||||
"no_results" = "Результатов нет";
|
||||
"s_only_performers" = "Только исполнители";
|
||||
"s_with_lyrics" = "С текстом";
|
||||
|
||||
/* BadBrowser */
|
||||
|
||||
|
|
Loading…
Reference in a new issue