add viewer and gallery

This commit is contained in:
mrilyew 2024-12-29 19:26:47 +03:00
parent 41b2947ba4
commit 6bb92aed3c
12 changed files with 309 additions and 24 deletions

View file

@ -156,6 +156,11 @@ class Document extends Media
return false;
}
function isImage(): bool
{
return in_array($this->getVKAPIType(), [3, 4]);
}
function isCopiedBy(User $user): bool
{
if($user->getId() === $this->getOwnerID())

View file

@ -2,6 +2,7 @@
namespace openvk\Web\Presenters;
use openvk\Web\Models\Repositories\{Documents, Clubs};
use openvk\Web\Models\Entities\Document;
use Nette\InvalidStateException as ISE;
final class DocumentsPresenter extends OpenVKPresenter
{
@ -10,6 +11,8 @@ final class DocumentsPresenter extends OpenVKPresenter
function renderList(?int $owner_id = NULL): void
{
$this->assertUserLoggedIn();
$this->template->_template = "Documents/List.xml";
if($owner_id > 0)
$this->notFound();
@ -110,6 +113,10 @@ final class DocumentsPresenter extends OpenVKPresenter
$document->save();
} catch(\TypeError $e) {
$this->flashFail("err", tr("forbidden"), $e->getMessage(), null, $isAjax);
} catch(ISE $e) {
$this->flashFail("err", tr("forbidden"), tr("error_file_preview"), null, $isAjax);
} catch(\ValueError $e) {
$this->flashFail("err", tr("forbidden"), $e->getMessage(), null, $isAjax);
}
if(!$isAjax) {
@ -121,4 +128,25 @@ final class DocumentsPresenter extends OpenVKPresenter
]);
}
}
function renderPage(int $virtual_id, int $real_id): void
{
$this->assertUserLoggedIn();
$access_key = $this->queryParam("key");
$doc = (new Documents)->getDocumentById((int)$virtual_id, (int)$real_id);
if(!$doc || $doc->isDeleted())
$this->notFound();
if(!$doc->checkAccessKey($access_key))
$this->notFound();
$this->template->doc = $doc;
$this->template->type = $doc->getVKAPIType();
$this->template->is_image = $doc->isImage();
$this->template->tags = $doc->getTags();
$this->template->copied = $doc->isCopiedBy($this->user->identity);
$this->template->copyImportance = true;
$this->template->modifiable = $doc->canBeModifiedBy($this->user->identity);
}
}

View file

@ -18,6 +18,7 @@
{/block}
{block content}
{var $is_gallery = $current_tab == 3 || $current_tab == 4}
<div id="docs_page_wrapper">
<div class="docs_page_search">
<form action="/search" method="get">
@ -36,7 +37,7 @@
</div>
</div>
</div>
<div class="docs_page_content">
<div n:class="docs_page_content, $is_gallery ? docs_page_gallery">
<div class="summaryBar display_flex_row display_flex_space_between">
<div class="summary">{tr($locale_string, $count)}.</div>
@ -49,16 +50,19 @@
<div class="container_white scroll_container">
{if $count > 0}
{foreach $docs as $doc}
<div class="scroll_node">
{include "components/doc.xml", doc => $doc}
</div>
{if $is_gallery}
{include "components/image.xml", doc => $doc, scroll_context => true}
{else}
{include "components/doc.xml", doc => $doc, scroll_context => true}
{/if}
{/foreach}
{else}
{include "../components/error.xml", description => tr("there_is_no_documents_alright")}
{/if}
{include "../components/paginator.xml", conf => $paginatorConf}
</div>
{include "../components/paginator.xml", conf => $paginatorConf}
</div>
</div>
{/block}

View file

@ -0,0 +1,74 @@
{extends "../@layout.xml"}
{block title}
{_document} "{ovk_proc_strtr($doc->getName(), 20)}"
{/block}
{block header}
{$doc->getName()}
{/block}
{block content}
<style>
.sidebar, .page_header, .page_footer {
opacity: 0;
pointer-events: none;
}
.page_body {
margin-top: -45px;
}
</style>
<div class='media-page-wrapper photo-page-wrapper'>
<div class='photo-page-wrapper-photo'>
{if $is_image}
<img alt="doc image" src="{$doc->getURL()}" />
{else}
<a href="{$doc->getURL()}" download="{downloadable_name($doc->getName())}">
<input class="button" type="button" value="{_download_file}">
</a>
{/if}
</div>
<div class='ovk-photo-details'>
<div class='media-page-wrapper-description'>
<p n:if='sizeof($tags) > 0'>
{foreach $tags as $tag}
<a href="/search?section=docs&tags={urlencode($tag)}">
{$tag}{if $tag != $tags[sizeof($tags) - 1]},{/if}
</a>
{/foreach}
</p>
<div class='upload_time'>
{_info_upload_date}: {$doc->getPublicationTime()}
</div>
</div>
<hr/>
<div class="media-page-wrapper-details">
<div class='media-page-wrapper-comments'></div>
<div class='media-page-wrapper-actions docMainItem' data-context="page" data-id="{$doc->getPrettiestId()}">
{if !$doc->isOwnerHidden()}
{var $owner = $doc->getOwner()}
<a href="{$owner->getURL()}" class='media-page-author-block'>
<img class='cCompactAvatars' src="{$owner->getAvatarURL('miniscule')}">
<div class='media-page-author-block-name'>
<b>{$owner->getCanonicalName()}</b>
</div>
</a>
{/if}
{if isset($thisUser)}
<a n:if="$modifiable" class="profile_link" style="display:block;width:96%;" id="edit_icon">{_edit}</a>
<a n:if="$modifiable" class="profile_link" style="display:block;width:96%;" id="report_icon">{_report}</a>
<a n:if="!$copied || $copied && $copyImportance" class="profile_link" style="display:block;width:96%;" id="add_icon">{_add}</a>
<a n:if="$copied && !$copyImportance" class="profile_link" style="display:block;width:96%;" id="remove_icon">{_remove}</a>
{/if}
</div>
</div>
</div>
</div>
{/block}

View file

@ -12,14 +12,13 @@
»
<a href="/docs">{_documents}</a>
{/if}
»
загрузка
Non-AJAX Document upload
{/block}
{block content}
<form method="post" enctype="multipart/form-data">
<table>
<table width="600">
<tbody>
<tr>
<td><span class="nobold">{_name}:</span></td>

View file

@ -3,8 +3,8 @@
{var $copied = $doc->isCopiedBy($thisUser)}
{var $modifiable = $doc->canBeModifiedBy($thisUser)}
<div class="docListViewItem" data-id="{$doc->getPrettiestId()}">
<a>
<div n:class="docMainItem, docListViewItem, $scroll_context ? scroll_node" data-id="{$doc->getPrettiestId()}">
<a class="viewerOpener" href="/doc{$doc->getPrettyId()}">
{if $preview}
<img class="doc_icon" alt="document_preview" src="{$preview->getURLBySizeId('tiny')}">
{else}
@ -14,7 +14,7 @@
{/if}
</a>
<div class="doc_content noOverflow">
<a><b class="noOverflow doc_name">{$doc->getName()}</b></a>
<a class="viewerOpener" href="/doc{$doc->getPrettyId()}"><b class="noOverflow doc_name">{$doc->getName()}</b></a>
<div class="doc_content_info">
<span>{$doc->getPublicationTime()}</span>,

View file

@ -0,0 +1,19 @@
{var $preview = $doc->hasPreview() ? $doc->getPreview() : NULL}
{var $copied = $doc->isCopiedBy($thisUser)}
{var $modifiable = $doc->canBeModifiedBy($thisUser)}
<a href="/doc{$doc->getPrettyId()}" n:class="docMainItem, viewerOpener, docGalleryItem, $scroll_context ? scroll_node" data-id="{$doc->getPrettiestId()}">
<img loading="lazy" src="{$preview->getURLBySizeId('medium')}" alt="gallery photo">
<div class="doc_top_panel doc_shown_by_hover">
<div n:if="!$modifiable" id="report_icon"></div>
<div n:if="$modifiable" id="edit_icon"></div>
<div n:if="!$copied || $copied && $copyImportance" id="add_icon"></div>
<div n:if="$copied && !$copyImportance" id="remove_icon"></div>
</div>
<div class="doc_bottom_panel doc_shown_by_hover doc_content">
<span class="doc_bottom_panel_name noOverflow doc_name">{$doc->getName()}</span>
<span class="doc_bottom_panel_size">{readable_filesize($doc->getFilesize())}</span>
</div>
</a>

View file

@ -317,6 +317,8 @@ routes:
handler: "Documents->listGroup"
- url: "/docs/upload"
handler: "Documents->upload"
- url: "/doc{num}_{num}"
handler: "Documents->page"
- url: "/admin"
handler: "Admin->index"
- url: "/admin/users"

View file

@ -4010,6 +4010,73 @@ hr {
padding: 5px 10px;
}
#docs_page_wrapper .docs_page_content.docs_page_gallery .scroll_container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 10px;
padding: 10px 10px;
}
#docs_page_wrapper .docs_page_content.docs_page_gallery .scroll_container .docGalleryItem {
height: 200px;
cursor: pointer;
position: relative;
/*width: 200px;*/
}
#docs_page_wrapper .docs_page_content.docs_page_gallery .scroll_container .docGalleryItem img {
width: 100%;
height: 100%;
display: block;
object-fit: cover;
}
#docs_page_wrapper .docs_page_content.docs_page_gallery .scroll_container .docGalleryItem .doc_bottom_panel {
position: absolute;
bottom: 0px;
background: rgba(1, 1, 1, 0.7);
padding: 6px 6px;
width: calc(100% - 12px);
display: grid;
grid-template-columns: 1fr 0fr;
}
.docGalleryItem .doc_shown_by_hover {
transition: all 100ms ease-in;
opacity: 0;
}
#docs_page_wrapper .docs_page_content.docs_page_gallery .scroll_container .docGalleryItem:hover .doc_shown_by_hover {
opacity: 1;
}
#docs_page_wrapper .docs_page_content.docs_page_gallery .scroll_container .docGalleryItem .doc_bottom_panel span {
color: white;
}
#docs_page_wrapper .docs_page_content.docs_page_gallery .scroll_container .docGalleryItem .doc_bottom_panel .doc_bottom_panel_size {
max-width: 49px;
width: max-content;
}
.docGalleryItem .doc_top_panel {
position: absolute;
top: 5px;
right: 5px;
background: rgba(1, 1, 1, 0.7);
padding: 6px 6px;
display: flex;
gap: 5px;
}
.docGalleryItem .doc_top_panel > div {
width: 10px;
height: 10px;
background-color: white;
}
#docs_page_wrapper select {
width: 150px;
}
@ -4023,7 +4090,7 @@ hr {
border-bottom: 1px solid #EDEDED;
}
.scroll_node:last-of-type .docListViewItem {
.docs_page_content .docListViewItem:last-of-type {
border-bottom: unset !important;
}
@ -4111,3 +4178,7 @@ hr {
.docListViewItem .doc_volume #mark_icon {
background-position-x: -80px;
}
.doc_viewer_wrapper {
overflow: hidden;
}

View file

@ -11,7 +11,7 @@ function showDocumentUploadDialog(target = null, append_to_url = null)
<li>${tr("limitations_file_author_rights")}.</li>
</ul>
<div style="text-align:center;margin: 10px 0px 2px 0px;">
<div id="_document_upload_frame" style="text-align:center;margin: 10px 0px 2px 0px;">
<input onclick="upload_btn.click()" class="button" type="button" value="${tr("select_file_fp")}">
<input id="upload_btn" type="file" style="display:none;">
</div>
@ -92,9 +92,19 @@ function showDocumentUploadDialog(target = null, append_to_url = null)
})
}
u(document).on('click', '.docListViewItem #edit_icon', async (e) => {
u(document).on("drop", "#_document_upload_frame", (e) => {
e.dataTransfer.dropEffect = 'move';
e.preventDefault()
u(`#_document_upload_frame #upload_btn`).nodes[0].files = e.dataTransfer.files
u("#_document_upload_frame #upload_btn").trigger("change")
})
u(document).on('click', '.docMainItem #edit_icon', async (e) => {
e.preventDefault()
const target = u(e.target).closest("#edit_icon")
const item = target.closest('.docListViewItem')
const item = target.closest('.docMainItem')
const id = item.nodes[0].dataset.id
CMessageBox.toggleLoader()
@ -109,7 +119,7 @@ u(document).on('click', '.docListViewItem #edit_icon', async (e) => {
<input type="text" name="doc_name" value="${doc.title}" placeholder="...">
<label>
<input maxlength="255" value="0" type="radio" name="doc_access" ${doc.folder_id == 0 ? "checked" : ''}>
<input maxlength="255" value="0" type="radio" name="doc_access" ${doc.folder_id != 3 ? "checked" : ''}>
${tr("private_document")}
</label>
<br>
@ -161,9 +171,12 @@ u(document).on('change', "#docs_page_wrapper select[name='docs_sort']", (e) => {
window.router.route(new_url.href)
})
u(document).on('click', '.docListViewItem #remove_icon', async (e) => {
const target = u(e.target).closest("#remove_icon")
const item = target.closest('.docListViewItem')
u(document).on('click', '.docMainItem #remove_icon', async (e) => {
e.preventDefault()
const target = u(e.target).closest("#remove_icon")
const item = target.closest('.docMainItem')
const context = item.attr('data-context')
const id = item.nodes[0].dataset.id.split("_")
target.addClass('lagged')
@ -172,13 +185,21 @@ u(document).on('click', '.docListViewItem #remove_icon', async (e) => {
if(res == 1) {
target.attr('id', 'mark_icon')
if(context == "page") {
target.html('✓')
window.router.route('/docs')
}
}
})
u(document).on('click', '.docListViewItem #add_icon', async (e) => {
u(document).on('click', '.docMainItem #add_icon', async (e) => {
e.preventDefault()
const target = u(e.target).closest("#add_icon")
const item = target.closest('.docListViewItem')
const item = target.closest('.docMainItem')
const id = item.nodes[0].dataset.id.split("_")
const context = item.attr('data-context')
target.addClass('lagged')
@ -192,11 +213,17 @@ u(document).on('click', '.docListViewItem #add_icon', async (e) => {
target.removeClass('lagged')
target.attr('id', 'mark_icon')
if(context == "page") {
target.html('✓')
}
})
u(document).on('click', '.docListViewItem #report_icon', (e) => {
u(document).on('click', '.docMainItem #report_icon', (e) => {
e.preventDefault()
const target = u(e.target).closest("#report_icon")
const item = target.closest('.docListViewItem')
const item = target.closest('.docMainItem')
const id = item.nodes[0].dataset.id.split("_")
MessageBox(tr("report_question"), `
@ -218,3 +245,50 @@ u(document).on('click', '.docListViewItem #report_icon', (e) => {
Function.noop])
})
u(document).on("click", ".docListViewItem a.viewerOpener, a.docGalleryItem", async (e) => {
e.preventDefault()
const target = u(e.target)
const link = target.closest('a')
CMessageBox.toggleLoader()
const url = link.nodes[0].href
const request = await fetch(url)
const body_html = await request.text()
const parser = new DOMParser
const body = parser.parseFromString(body_html, "text/html")
const preview = body.querySelector('.photo-page-wrapper-photo')
const details = body.querySelector('.ovk-photo-details')
preview.querySelector('img').setAttribute('id', 'ovk-photo-img')
const photo_viewer = new CMessageBox({
title: '',
custom_template: u(`
<div class="ovk-photo-view-dimmer">
<div class="ovk-photo-view">
<div class="photo_com_title">
<text id="photo_com_title_photos">
${tr("document")}
</text>
<div>
<a id="ovk-photo-close">${tr("close")}</a>
</div>
</div>
<div class='photo_viewer_wrapper doc_viewer_wrapper'>
${preview.innerHTML}
</div>
<div class="ovk-photo-details">
${details.innerHTML}
</div>
</div>
</div>`)
})
photo_viewer.getNode().find("#ovk-photo-close").on("click", function(e) {
photo_viewer.close()
});
CMessageBox.toggleLoader()
})

View file

@ -387,6 +387,11 @@ function readable_filesize($bytes, $precision = 2): string
return round($bytes, $precision) . $units[$power];
}
function downloadable_name(string $text): string
{
return preg_replace('/[\\/:*?"<>|]/', '_', str_replace(' ', '_', $text));
}
return (function() {
_ovk_check_environment();
require __DIR__ . "/vendor/autoload.php";

View file

@ -2222,7 +2222,10 @@
"tags" = "Теги";
"owner_is_hidden" = "Автор скрыт";
"accessbility" = "Доступность";
"download_file" = "Скачать файл";
"remove" = "Удалить";
"document" = "Документ";
"document_type_0" = "Все";
"document_type_1" = "Текстовые";
"document_type_2" = "Архивы";
@ -2259,6 +2262,7 @@
"error_file_too_big" = "Файл слишком большой.";
"error_file_invalid_format" = "Формат файла не разрешён.";
"error_file_adding_copied" = "Не удалось добавить файл; он уже добавлен.";
"error_file_preview" = "Не удалось загрузить файл: изображение имеет странности.";
"private_document" = "Приватный (по ссылке)";
"public_document" = "Публичный";