From 0151b3d0209955a50b943d0a939ecce34bb3c4c0 Mon Sep 17 00:00:00 2001 From: n1rwana Date: Mon, 14 Aug 2023 14:27:31 +0300 Subject: [PATCH] =?UTF-8?q?=D0=98=D0=BD=D1=82=D0=B5=D1=80=D1=84=D0=B5?= =?UTF-8?q?=D0=B9=D1=81=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Web/Models/Entities/Audio.php | 40 ++- Web/Models/Entities/Playlist.php | 18 +- Web/Models/Entities/User.php | 1 + Web/Models/Repositories/Audios.php | 55 +++- Web/Presenters/AdminPresenter.php | 67 ++++- Web/Presenters/AudioPresenter.php | 274 +++++++++++++++++- Web/Presenters/UserPresenter.php | 1 + Web/Presenters/templates/@layout.xml | 1 + Web/Presenters/templates/Admin/@layout.xml | 3 + Web/Presenters/templates/Admin/EditMusic.xml | 65 +++++ .../templates/Admin/EditPlaylist.xml | 54 ++++ Web/Presenters/templates/Admin/Music.xml | 135 +++++++++ Web/Presenters/templates/Audio/List.xml | 86 ++++++ Web/Presenters/templates/Audio/New.xml | 29 ++ .../templates/Audio/NewPlaylist.xml | 81 ++++++ Web/Presenters/templates/Audio/Playlist.xml | 110 +++++++ Web/Presenters/templates/Audio/Popular.xml | 29 ++ Web/Presenters/templates/Audio/Search.xml | 77 +++++ Web/Presenters/templates/Audio/player.js.xml | 144 +++++++++ Web/Presenters/templates/Audio/player.xml | 57 ++++ Web/Presenters/templates/Audio/tabs.xml | 73 +++++ Web/Presenters/templates/User/Settings.xml | 11 + Web/routes.yml | 16 + Web/static/img/common.png | Bin 0 -> 1890 bytes Web/static/img/song.jpg | Bin 0 -> 2763 bytes install/sqls/gamma-00000-disco.sql | 1 + 26 files changed, 1395 insertions(+), 33 deletions(-) create mode 100644 Web/Presenters/templates/Admin/EditMusic.xml create mode 100644 Web/Presenters/templates/Admin/EditPlaylist.xml create mode 100644 Web/Presenters/templates/Admin/Music.xml create mode 100644 Web/Presenters/templates/Audio/List.xml create mode 100644 Web/Presenters/templates/Audio/New.xml create mode 100644 Web/Presenters/templates/Audio/NewPlaylist.xml create mode 100644 Web/Presenters/templates/Audio/Playlist.xml create mode 100644 Web/Presenters/templates/Audio/Popular.xml create mode 100644 Web/Presenters/templates/Audio/Search.xml create mode 100644 Web/Presenters/templates/Audio/player.js.xml create mode 100644 Web/Presenters/templates/Audio/player.xml create mode 100644 Web/Presenters/templates/Audio/tabs.xml create mode 100644 Web/static/img/common.png create mode 100644 Web/static/img/song.jpg diff --git a/Web/Models/Entities/Audio.php b/Web/Models/Entities/Audio.php index 5d6d5783..03f0ed0b 100644 --- a/Web/Models/Entities/Audio.php +++ b/Web/Models/Entities/Audio.php @@ -14,6 +14,7 @@ class Audio extends Media { protected $tableName = "audios"; protected $fileExtension = "mpd"; + // protected $fileExtension = "mp3"; # Taken from winamp :D const genres = [ @@ -100,10 +101,10 @@ class Audio extends Media $this->stateChanges("segment_size", $ss); $this->stateChanges("length", $duration); - try { + try { $args = [ - OPENVK_ROOT, - $this->getBaseDir(), + str_replace("enabled", "available", OPENVK_ROOT), + str_replace("enabled", "available", $this->getBaseDir()), $hash, $filename, @@ -113,21 +114,27 @@ class Audio extends Media $ss, ]; - if(Shell::isPowershell()) + + if(Shell::isPowershell()) { Shell::powershell("-executionpolicy bypass", "-File", __DIR__ . "/../shell/processAudio.ps1", ...$args) ->start(); - else + } 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'); + } # 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); - } + } catch(UnknownCommandException $ucex) { + exit(OPENVK_ROOT_CONF["openvk"]["debug"] ? "bash/pwsh is not installed" : VIDEOS_FRIENDLY_ERROR); + } return true; } @@ -199,6 +206,13 @@ class Audio extends Media return str_replace(".mpd", "_fragments", $this->getURL()) . "/original_$key.mp3"; } + function getURL(?bool $force = false): string + { + if ($this->isWithdrawn()) return ""; + + return parent::getURL(); + } + function getKeys(): array { $keys[bin2hex($this->getRecord()->kid)] = bin2hex($this->getRecord()->key); @@ -312,7 +326,7 @@ class Audio extends Media ]); if($entity instanceof User) { - $this->stateChanges("listens", $this->getListens() + 1); + $this->stateChanges("listens", ($this->getListens() + 1)); $this->save(); } diff --git a/Web/Models/Entities/Playlist.php b/Web/Models/Entities/Playlist.php index e79f8ab6..90a517d1 100644 --- a/Web/Models/Entities/Playlist.php +++ b/Web/Models/Entities/Playlist.php @@ -3,6 +3,7 @@ namespace openvk\Web\Models\Entities; use Chandler\Database\DatabaseConnection; use Nette\Database\Table\ActiveRow; use openvk\Web\Models\Repositories\Audios; +use openvk\Web\Models\Repositories\Photos; use openvk\Web\Models\RowModel; /** @@ -31,7 +32,8 @@ class Playlist extends MediaCollection function getCoverURL(): ?string { - return NULL; + $photo = (new Photos)->get((int) $this->getRecord()->cover_photo_id); + return is_null($photo) ? "/assets/packages/static/openvk/img/song.jpg" : $photo->getURL(); } function getLength(): int @@ -158,4 +160,18 @@ class Playlist extends MediaCollection parent::delete($softly); } + + function hasAudio(Audio $audio): bool + { + $ctx = DatabaseConnection::i()->getContext(); + return !is_null($ctx->table("playlist_relations")->where([ + "collection" => $this->getId(), + "media" => $audio->getId() + ])->fetch()); + } + + function getCoverPhotoId(): ?int + { + return $this->getRecord()->cover_photo_id; + } } \ No newline at end of file diff --git a/Web/Models/Entities/User.php b/Web/Models/Entities/User.php index 31d8f4c8..f60d12e3 100644 --- a/Web/Models/Entities/User.php +++ b/Web/Models/Entities/User.php @@ -403,6 +403,7 @@ class User extends RowModel "length" => 1, "mappings" => [ "photos", + "audios", "videos", "messages", "notes", diff --git a/Web/Models/Repositories/Audios.php b/Web/Models/Repositories/Audios.php index dfeb8978..45a3b8df 100644 --- a/Web/Models/Repositories/Audios.php +++ b/Web/Models/Repositories/Audios.php @@ -14,6 +14,7 @@ class Audios private $rels; private $playlists; private $playlistImports; + private $playlistRels; const ORDER_NEW = 0; const ORDER_POPULAR = 1; @@ -30,6 +31,7 @@ class Audios $this->playlists = $this->context->table("playlists"); $this->playlistImports = $this->context->table("playlist_imports"); + $this->playlistRels = $this->context->table("playlist_relations"); } function get(int $id): ?Audio @@ -61,6 +63,17 @@ class Audios return new Audio($audio); } + function getPlaylistByOwnerAndVID(int $owner, int $vId): ?Playlist + { + $playlist = $this->playlists->where([ + "owner" => $owner, + "id" => $vId, + ])->fetch(); + if(!$playlist) return NULL; + + return new Playlist($playlist); + } + function getByEntityID(int $entity, int $offset = 0, ?int $limit = NULL, ?int& $deleted = nullptr): \Traversable { $limit ??= OPENVK_DEFAULT_PER_PAGE; @@ -158,7 +171,7 @@ class Audios function search(string $query, int $sortMode = 0, bool $performerOnly = false, bool $withLyrics = false): EntityStream { $columns = $performerOnly ? "performer" : "performer, name"; - $order = (["created", "length", "listens"][$sortMode] ?? "") . "DESC"; + $order = (["created", "length", "listens"][$sortMode] ?? "") . " DESC"; $search = $this->audios->where([ "unlisted" => 0, @@ -173,10 +186,44 @@ class Audios function searchPlaylists(string $query): EntityStream { - $search = $this->audios->where([ + $search = $this->playlists->where([ "deleted" => 0, - ])->where("MATCH (title, description) AGAINST (? IN BOOLEAN MODE)", $query); + ])->where("MATCH (`name`, `description`) AGAINST (? IN BOOLEAN MODE)", $query); return new EntityStream("Playlist", $search); } -} \ No newline at end of file + + function getNew(): EntityStream + { + return new EntityStream("Audio", $this->audios->where("created >= " . (time() - 259200))->order("created DESC")->limit(25)); + } + + function getPopular(): EntityStream + { + return new EntityStream("Audio", $this->audios->where("listens > 0")->order("listens DESC")->limit(25)); + } + + function isAdded(int $user_id, int $audio_id): bool + { + return !is_null($this->rels->where([ + "entity" => $user_id, + "audio" => $audio_id + ])->fetch()); + } + + function find(string $query, int $page = 1, ?int $perPage = NULL): \Traversable + { + $query = "%$query%"; + $result = $this->audios->where("name LIKE ? OR performer LIKE ?", $query, $query); + + return new Util\EntityStream("Audio", $result); + } + + function findPlaylists(string $query, int $page = 1, ?int $perPage = NULL): \Traversable + { + $query = "%$query%"; + $result = $this->playlists->where("name LIKE ?", $query); + + return new Util\EntityStream("Playlist", $result); + } +} diff --git a/Web/Presenters/AdminPresenter.php b/Web/Presenters/AdminPresenter.php index 530cb7f8..691291e2 100644 --- a/Web/Presenters/AdminPresenter.php +++ b/Web/Presenters/AdminPresenter.php @@ -1,7 +1,15 @@ users = $users; $this->clubs = $clubs; @@ -21,7 +30,8 @@ final class AdminPresenter extends OpenVKPresenter $this->gifts = $gifts; $this->bannedLinks = $bannedLinks; $this->chandlerGroups = $chandlerGroups; - + $this->audios = $audios; + parent::__construct(); } @@ -39,6 +49,15 @@ final class AdminPresenter extends OpenVKPresenter $count = $repo->find($query)->size(); return $repo->find($query)->page($page, 20); } + + private function searchPlaylists(&$count) + { + $query = $this->queryParam("q") ?? ""; + $page = (int) ($this->queryParam("p") ?? 1); + + $count = $this->audios->findPlaylists($query)->size(); + return $this->audios->findPlaylists($query)->page($page, 20); + } function onStartup(): void { @@ -547,4 +566,46 @@ final class AdminPresenter extends OpenVKPresenter $this->redirect("/admin/users/id" . $user->getId()); } + + function renderMusic(): void + { + $this->template->mode = in_array($this->queryParam("act"), ["audios", "playlists"]) ? $this->queryParam("act") : "audios"; + if ($this->template->mode === "audios") + $this->template->audios = $this->searchResults($this->audios, $this->template->count); + else + $this->template->playlists = $this->searchPlaylists($this->template->count); + } + + function renderEditMusic(int $audio_id): void + { + $audio = $this->audios->get($audio_id); + $this->template->audio = $audio; + + if ($_SERVER["REQUEST_METHOD"] === "POST") { + $audio->setName($this->postParam("name")); + $audio->setPerformer($this->postParam("performer")); + $audio->setLyrics($this->postParam("text")); + $audio->setGenre($this->postParam("genre")); + $audio->setOwner((int) $this->postParam("owner")); + $audio->setExplicit(!empty($this->postParam("explicit"))); + $audio->setDeleted(!empty($this->postParam("deleted"))); + $audio->setWithdrawn(!empty($this->postParam("withdrawn"))); + $audio->save(); + } + } + + function renderEditPlaylist(int $playlist_id): void + { + $playlist = $this->audios->getPlaylist($playlist_id); + $this->template->playlist = $playlist; + + if ($_SERVER["REQUEST_METHOD"] === "POST") { + $playlist->setName($this->postParam("name")); + $playlist->setDescription($this->postParam("description")); + $playlist->setCover_Photo_Id((int) $this->postParam("photo")); + $playlist->setOwner((int) $this->postParam("owner")); + $playlist->setDeleted(!empty($this->postParam("deleted"))); + $playlist->save(); + } + } } diff --git a/Web/Presenters/AudioPresenter.php b/Web/Presenters/AudioPresenter.php index 05b1c4e8..70c23139 100644 --- a/Web/Presenters/AudioPresenter.php +++ b/Web/Presenters/AudioPresenter.php @@ -1,6 +1,10 @@ renderApp("_popular"); + $this->renderList(NULL, "popular"); } function renderNew(): void { - $this->renderApp("_new"); + $this->renderList(NULL, "new"); } - function renderList(int $owner): void + function renderList(?int $owner = NULL, ?string $mode = "list"): void { - $entity = NULL; - if($owner < 0) - $entity = (new Clubs)->get($owner); - else - $entity = (new Users)->get($owner); + $this->template->_template = "Audio/List"; - if(!$entity) - $this->notFound(); + $audios = []; + $playlists = []; - $this->renderApp("owner=$owner"); + if ($mode === "list") { + $entity = NULL; + if ($owner < 0) { + $entity = (new Clubs)->get($owner * -1); + if (!$entity || $entity->isBanned()) + $this->redirect("/audios" . $this->user->id); + + $audios = $this->audios->getByClub($entity); + $playlists = $this->audios->getPlaylistsByClub($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); + } + + if (!$entity) + $this->notFound(); + + $this->template->owner = $entity; + $this->template->isMy = ($owner > 0 && ($entity->getId() === $this->user->id)); + $this->template->isMyClub = ($owner < 0 && $entity->canBeModifiedBy($this->user->identity)); + } else if ($mode === "new") { + $audios = $this->audios->getNew(); + } else { + $audios = $this->audios->getPopular(); + } + + // $this->renderApp("owner=$owner"); + if ($audios !== []) + $this->template->audios = iterator_to_array($audios); + + if ($playlists !== []) + $this->template->playlists = iterator_to_array($playlists); } function renderView(int $owner, int $id): void @@ -59,7 +94,21 @@ final class AudioPresenter extends OpenVKPresenter if(!$audio->canBeViewedBy($this->user->identity)) $this->flashFail("err", tr("forbidden"), tr("forbidden_comment")); - $this->renderApp("id=" . $audio->getId()); + if ($_SERVER["REQUEST_METHOD"] === "POST") { + switch ($this->queryParam("act")) { + case "remove": + DatabaseConnection::i()->getContext()->query("DELETE FROM `audio_relations` WHERE `entity` = ? AND `audio` = ?", $this->user->id, $audio->getId()); + break; + + case "edit": + break; + + default: + $this->returnJson(["success" => false, "error" => "Action not implemented or not exists"]); + } + } else { + $this->renderApp("id=" . $audio->getId()); + } } function renderEmbed(int $owner, int $id): void @@ -144,4 +193,205 @@ final class AudioPresenter extends OpenVKPresenter $this->redirect(is_null($group) ? "/audios" . $this->user->id : "/audios-" . $group->getId()); } + + function renderListen(int $id): void + { + if ($_SERVER["REQUEST_METHOD"] === "POST") { + $this->assertUserLoggedIn(); + $this->assertNoCSRF(); + + $audio = $this->audios->get($id); + + if ($audio && !$audio->isDeleted() && !$audio->isWithdrawn()) { + $audio->listen($this->user->identity); + } + + $this->returnJson(["response" => true]); + } + } + + 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)); + } + } + + function renderNewPlaylist(): void + { + $owner = $this->user->id; + + if ($this->requestParam("owner")) { + $club = (new Clubs)->get((int) $this->requestParam("owner") * -1); + if (!$club || $club->isBanned() || !$club->canBeModifiedBy($this->user->identity)) + $this->redirect("/audios" . $this->user->id); + + $owner = ($club->getId() * -1); + } + + $this->template->owner = $owner; + + // exit(var_dump($owner)); + + if ($_SERVER["REQUEST_METHOD"] === "POST") { + $title = $this->postParam("title"); + $description = $this->postParam("description"); + $audios = !empty($this->postParam("audios")) ? explode(",", $this->postParam("audios")) : []; + + if (!$title) + $this->returnJson(["success" => false, "error" => "Название не указано"]); + + $playlist = new Playlist; + $playlist->setOwner($owner); + $playlist->setName(substr($title, 0, 128)); + $playlist->setDescription(substr($description, 0, 2048)); + $playlist->save(); + + foreach ($audios as $audio) { + DatabaseConnection::i()->getContext()->query("INSERT INTO `playlist_relations` (`collection`, `media`) VALUES (?, ?)", $playlist->getId(), $audio); + } + + DatabaseConnection::i()->getContext()->query("INSERT INTO `playlist_imports` (`entity`, `playlist`) VALUES (?, ?)", $owner, $playlist->getId()); + + $this->returnJson(["success" => true, "payload" => "/playlist" . $owner . "_" . $playlist->getId()]); + } else { + $this->template->audios = iterator_to_array($this->audios->getByUser($this->user->identity)); + } + } + + function renderPlaylist(int $owner_id, int $virtual_id): void + { + $playlist = $this->audios->getPlaylistByOwnerAndVID($owner_id, $virtual_id); + + if (!$playlist || $playlist->isDeleted()) + $this->notFound(); + + $this->template->playlist = $playlist; + $this->template->audios = iterator_to_array($playlist->getAudios()); + $this->template->isMy = $playlist->getOwner()->getId() === $this->user->id; + $this->template->canEdit = ($this->template->isMy || ($playlist->getOwner() instanceof Club && $playlist->getOwner()->canBeModifiedBy($this->user->identity))); + $this->template->edit = $this->queryParam("act") === "edit"; + + if ($this->template->edit) { + if (!$this->template->canEdit) { + $this->flashFail("err", tr("error"), tr("forbidden")); + } + + $_ids = []; + $audios = iterator_to_array($playlist->getAudios()); + foreach ($audios as $audio) { + $_ids[] = $audio->getId(); + } + + foreach ($this->audios->getByUser($this->user->identity) as $audio) { + if (!in_array($audio->getId(), $_ids)) { + $audios[] = $audio; + } + } + + $this->template->audios = $audios; + } else { + $this->template->audios = iterator_to_array($playlist->getAudios()); + } + + if ($_SERVER["REQUEST_METHOD"] === "POST") { + if (!$this->template->canEdit) { + $this->flashFail("err", tr("error"), tr("forbidden")); + } + + $title = $this->postParam("title"); + $description = $this->postParam("description"); + $audios = !empty($this->postParam("audios")) ? explode(",", $this->postParam("audios")) : []; + + $playlist->setName(substr($title, 0, 128)); + $playlist->setDescription(substr($description, 0, 2048)); + $playlist->setEdited(time()); + + if ($_FILES["cover"]["error"] === UPLOAD_ERR_OK) { + $photo = new Photo; + $photo->setOwner($this->user->id); + $photo->setDescription("Playlist #" . $playlist->getId() . " cover image"); + $photo->setFile($_FILES["cover"]); + $photo->setCreated(time()); + $photo->save(); + + $playlist->setCover_Photo_Id($photo->getId()); + } + + $playlist->save(); + + $_ids = []; + + foreach ($playlist->getAudios() as $audio) { + $_ids[] = $audio->getId(); + } + + foreach ($playlist->getAudios() as $audio) { + if (!in_array($audio->getId(), $audios)) { + DatabaseConnection::i()->getContext()->query("DELETE FROM `playlist_relations` WHERE `collection` = ? AND `media` = ?", $playlist->getId(), $audio->getId()); + } + } + + foreach ($audios as $audio) { + if (!in_array($audio, $_ids)) { + DatabaseConnection::i()->getContext()->query("INSERT INTO `playlist_relations` (`collection`, `media`) VALUES (?, ?)", $playlist->getId(), $audio); + } + } + + $this->flash("succ", tr("changes_saved")); + $this->redirect("/playlist" . $playlist->getOwner()->getId() . "_" . $playlist->getId()); + } + } + + function renderAction(int $audio_id): void + { + 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" => "Аудиозапись уже добавлена"]); + } + 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; + + case "edit": + $audio = $this->audios->get($audio_id); + if (!$audio || $audio->isDeleted() || $audio->isWithdrawn() || $audio->isUnlisted()) + $this->returnJson(["success" => false, "error" => "Аудиозапись не найдена"]); + + if ($audio->getOwner()->getId() !== $this->user->id) + $this->returnJson(["success" => false, "error" => "Ошибка доступа"]); + + $performer = $this->postParam("performer"); + $name = $this->postParam("name"); + $lyrics = $this->postParam("lyrics"); + $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")); + + $audio->setName($name); + $audio->setPerformer($performer); + $audio->setLyrics(empty($lyrics) ? NULL : $lyrics); + $audio->setGenre($genre); + $audio->save(); + break; + + default: + break; + } + + $this->returnJson(["success" => true]); + } } \ No newline at end of file diff --git a/Web/Presenters/UserPresenter.php b/Web/Presenters/UserPresenter.php index 3cf68757..74c3523c 100644 --- a/Web/Presenters/UserPresenter.php +++ b/Web/Presenters/UserPresenter.php @@ -455,6 +455,7 @@ final class UserPresenter extends OpenVKPresenter } else if($_GET['act'] === "lMenu") { $settings = [ "menu_bildoj" => "photos", + "menu_muziko" => "audios", "menu_filmetoj" => "videos", "menu_mesagoj" => "messages", "menu_notatoj" => "notes", diff --git a/Web/Presenters/templates/@layout.xml b/Web/Presenters/templates/@layout.xml index 8cff0bb8..d7d112d3 100644 --- a/Web/Presenters/templates/@layout.xml +++ b/Web/Presenters/templates/@layout.xml @@ -122,6 +122,7 @@ {_my_photos} + Мои Аудиозаписи {_my_videos} {_my_messages} diff --git a/Web/Presenters/templates/Admin/@layout.xml b/Web/Presenters/templates/Admin/@layout.xml index 788bba8d..8408aa93 100644 --- a/Web/Presenters/templates/Admin/@layout.xml +++ b/Web/Presenters/templates/Admin/@layout.xml @@ -59,6 +59,9 @@
  • {_admin_banned_links}
  • +
  • + Музыка +
  • Chandler diff --git a/Web/Presenters/templates/Admin/EditMusic.xml b/Web/Presenters/templates/Admin/EditMusic.xml new file mode 100644 index 00000000..7ae856f1 --- /dev/null +++ b/Web/Presenters/templates/Admin/EditMusic.xml @@ -0,0 +1,65 @@ +{extends "@layout.xml"} + +{block title} + {_edit} {$audio->getName()} +{/block} + +{block heading} + {$audio->getName()} +{/block} + +{block content} +
    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    + + +
    +
    + + isExplicit()} checked {/if} /> +
    +
    + + isDeleted()} checked {/if} /> +
    +
    + + isWithdrawn()} checked {/if} /> +
    +
    +
    +
    + + +
    +
    +
    +
    +{/block} diff --git a/Web/Presenters/templates/Admin/EditPlaylist.xml b/Web/Presenters/templates/Admin/EditPlaylist.xml new file mode 100644 index 00000000..7c6e21cd --- /dev/null +++ b/Web/Presenters/templates/Admin/EditPlaylist.xml @@ -0,0 +1,54 @@ +{extends "@layout.xml"} + +{block title} + {_edit} {$playlist->getName()} +{/block} + +{block heading} + {$playlist->getName()} +{/block} + +{block content} +
    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + + + + + +
    + +
    +
    +
    + + +
    +
    + + isDeleted()} checked {/if} /> +
    +
    +
    +
    + + +
    +
    +
    +
    +{/block} diff --git a/Web/Presenters/templates/Admin/Music.xml b/Web/Presenters/templates/Admin/Music.xml new file mode 100644 index 00000000..a2b61a0d --- /dev/null +++ b/Web/Presenters/templates/Admin/Music.xml @@ -0,0 +1,135 @@ +{extends "@layout.xml"} +{var $search = $mode === "audios"} + +{block title} + Поиск музыки +{/block} + +{block heading} + Музыка +{/block} + +{block searchTitle} + {include title} +{/block} + +{block content} + + + + {if $mode === "audios"} + {var $audios = iterator_to_array($audios)} + {var $amount = sizeof($audios)} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {else} + {var $playlists = iterator_to_array($playlists)} + {var $amount = sizeof($playlists)} + + + + + + + + + + + + + + + + + + + {/if} +
    ID{_admin_author}Исполнитель{_admin_title}ЖанрExplicitИзъятоУдаленоСозданДействия
    {$audio->getId()} + {var $owner = $audio->getOwner()} + + + {$owner->getCanonicalName()} + + + + {$owner->getCanonicalName()} + + {_admin_banned} + {$audio->getPerformer()}{$audio->getTitle()}{$audio->getGenre()}{$audio->isExplicit() ? tr("yes") : tr("no")} + {$audio->isWithdrawn() ? tr("yes") : tr("no")} + + {$audio->isDeleted() ? tr("yes") : tr("no")} + {$audio->getPublicationTime()} + + Редактировать + +
    ID{_admin_author}НазваниеСозданДействия
    {$playlist->getId()} + {var $owner = $playlist->getOwner()} + + + {$owner->getCanonicalName()} + + + + {$owner->getCanonicalName()} + + {_admin_banned} + + + + {$owner->getCanonicalName()} + + + {$playlist->getName()} + {$playlist->getCreationTime()} + + Редактировать + +
    +
    +
    + {var $isLast = ((10 * (($_GET['p'] ?? 1) - 1)) + $amount) < $count} + + « + » +
    +{/block} diff --git a/Web/Presenters/templates/Audio/List.xml b/Web/Presenters/templates/Audio/List.xml new file mode 100644 index 00000000..e9b8a686 --- /dev/null +++ b/Web/Presenters/templates/Audio/List.xml @@ -0,0 +1,86 @@ +{extends "../@layout.xml"} + +{block title}Аудиозаписи{/block} + +{block header} +
    +
    Мои аудиозаписи
    +
    + {$owner->getCanonicalName()} + » + Аудиозаписи +
    +
    +{/block} + +{block content} + {include "tabs.xml", mode => "list", listText => ($isMy ? "Моя музыка" : $owner->getCanonicalName())} + + +
    +

    +
    Плейлисты
    +
    +
    +
    +

    +
    +
    + {include "../components/nothing.xml"} +
    +
    +
    +
    +
    +
    {$playlist->getName()}
    +
    +
    +
    +
    + +

    +
    Музыка
    +
    +
    +
    +

    +
    +
    + {include "../components/nothing.xml"} +
    +
    + {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} +
    + +
    + {script "js/node_modules/umbrellajs/umbrella.min.js"} + {script "js/node_modules/dashjs/dist/dash.all.min.js"} + + {include "player.js.xml", audios => $audios} +
    +
    +
    +
    + {include "../components/nothing.xml"} +
    +{/block} diff --git a/Web/Presenters/templates/Audio/New.xml b/Web/Presenters/templates/Audio/New.xml new file mode 100644 index 00000000..f4e54d69 --- /dev/null +++ b/Web/Presenters/templates/Audio/New.xml @@ -0,0 +1,29 @@ +{extends "../@layout.xml"} + +{block title}Аудиозаписи{/block} + +{block header} + Новое +{/block} + +{block content} + {include "tabs.xml", mode => "new"} + +
    +
    + {include "../components/nothing.xml"} +
    +
    + {foreach $audios as $audio} + {include "player.xml", audio => $audio, addOnClick => "addAudio({$audio->getId()})"} + {/foreach} +
    + +
    + {script "js/node_modules/umbrellajs/umbrella.min.js"} + {script "js/node_modules/dashjs/dist/dash.all.min.js"} + + {include "player.js.xml", audios => $audios} +
    +
    +{/block} diff --git a/Web/Presenters/templates/Audio/NewPlaylist.xml b/Web/Presenters/templates/Audio/NewPlaylist.xml new file mode 100644 index 00000000..4c600406 --- /dev/null +++ b/Web/Presenters/templates/Audio/NewPlaylist.xml @@ -0,0 +1,81 @@ +{extends "../@layout.xml"} + +{block title} + Создать плейлист +{/block} + +{block header} + {include title} +{/block} + +{block content} + {include "tabs.xml", mode => "list"} +
    + + + +
    + +

    + + {/if} +
    + создан {$playlist->getCreationTime()} + {if $playlist->getEditTime()} + · + обновлен {$playlist->getEditTime()} + {/if} +
    +
    +

    +
    +
    + {include "../components/nothing.xml"} +
    +
    + {include "player.xml", + audio => $audio, + canAdd => $edit && !$playlist->hasAudio($audio), + canRemove => $edit && $playlist->hasAudio($audio), + addOnClick => "addToPlaylist({$audio->getId()})", + deleteOnClick => "removeFromPlaylist({$audio->getId()})" + } +
    + +
    + {script "js/node_modules/umbrellajs/umbrella.min.js"} + {script "js/node_modules/dashjs/dist/dash.all.min.js"} + + {include "player.js.xml", audios => $audios} +
    +
    + {if $edit} +

    +
    + + + + {/if} + + + {if $edit}{/if} + +{/block} diff --git a/Web/Presenters/templates/Audio/Popular.xml b/Web/Presenters/templates/Audio/Popular.xml new file mode 100644 index 00000000..fccb7139 --- /dev/null +++ b/Web/Presenters/templates/Audio/Popular.xml @@ -0,0 +1,29 @@ +{extends "../@layout.xml"} + +{block title}Аудиозаписи{/block} + +{block header} + Популярное +{/block} + +{block content} + {include "tabs.xml", mode => "popular"} + +
    +
    + {include "../components/nothing.xml"} +
    +
    + {foreach $audios as $audio} + {include "player.xml", audio => $audio, addOnClick => "addAudio({$audio->getId()})"} + {/foreach} +
    + +
    + {script "js/node_modules/umbrellajs/umbrella.min.js"} + {script "js/node_modules/dashjs/dist/dash.all.min.js"} + + {include "player.js.xml", audios => $audios} +
    +
    +{/block} diff --git a/Web/Presenters/templates/Audio/Search.xml b/Web/Presenters/templates/Audio/Search.xml new file mode 100644 index 00000000..830c9698 --- /dev/null +++ b/Web/Presenters/templates/Audio/Search.xml @@ -0,0 +1,77 @@ +{extends "../@layout.xml"} + +{block title}Аудиозаписи{/block} + +{block header} + Поиск +{/block} + +{block content} + + {include "tabs.xml", mode => "search"} + +
    +
    + +
    +
    исполнитель
    + +
    +
    + +
    + +

    Плейлисты

    +
    +
    + {include "../components/nothing.xml"} +
    +
    +
    +
    +
    + +
    +
    {$playlist->getName()}
    + + {$playlist->getOwner()->getCanonicalName()} + +
    +
    +
    +
    + +

    Треки

    +
    + {include "../components/nothing.xml"} +
    +
    + {include "player.xml", audio => $audio, addOnClick => "addAudio({$audio->getId()})"} +
    +
    + +
    + {script "js/node_modules/umbrellajs/umbrella.min.js"} + {script "js/node_modules/dashjs/dist/dash.all.min.js"} + + {include "player.js.xml", audios => $audios} +
    +
    +
    + {include "../components/nothing.xml"} +
    +
    +{/block} diff --git a/Web/Presenters/templates/Audio/player.js.xml b/Web/Presenters/templates/Audio/player.js.xml new file mode 100644 index 00000000..2d0704fa --- /dev/null +++ b/Web/Presenters/templates/Audio/player.js.xml @@ -0,0 +1,144 @@ + diff --git a/Web/Presenters/templates/Audio/player.xml b/Web/Presenters/templates/Audio/player.xml new file mode 100644 index 00000000..58910a51 --- /dev/null +++ b/Web/Presenters/templates/Audio/player.xml @@ -0,0 +1,57 @@ +
    +