early implementation of photos pickir

Добавлен пикер фоточек и быстрая загрузка фото. Так же пофикшен просмотрщик фото в группах. Но, правда, я сломал копипейст, но это ладн.
This commit is contained in:
lalka2016 2023-09-19 17:05:41 +03:00
parent b1910a9ce9
commit f0490bd05e
11 changed files with 367 additions and 75 deletions

65
ServiceAPI/Photos.php Normal file
View file

@ -0,0 +1,65 @@
<?php declare(strict_types=1);
namespace openvk\ServiceAPI;
use openvk\Web\Models\Entities\User;
use openvk\Web\Models\Repositories\{Photos as PhotosRepo, Albums};
class Photos implements Handler
{
protected $user;
protected $photos;
function __construct(?User $user)
{
$this->user = $user;
$this->photos = new PhotosRepo;
}
function getPhotos(int $page = 1, int $album = 0, callable $resolve, callable $reject)
{
if($album == 0) {
$photos = $this->photos->getEveryUserPhoto($this->user, $page, 24);
$count = $this->photos->getUserPhotosCount($this->user);
} else {
$album = (new Albums)->get($album);
if(!$album || $album->isDeleted() || $album->getOwner()->getId() != $this->user->getId())
$reject(55, "Invalid .");
$photos = $album->getPhotos($page, 24);
$count = $album->size();
}
$arr = [
"count" => $count,
"items" => [],
];
foreach($photos as $photo) {
$res = json_decode(json_encode($photo->toVkApiStruct()), true);
$arr["items"][] = $res;
}
$resolve($arr);
}
function getAlbums(callable $resolve, callable $reject)
{
$albumsRepo = (new Albums);
$count = $albumsRepo->getUserAlbumsCount($this->user);
$albums = $albumsRepo->getUserAlbums($this->user, 1, $count);
$arr = [
"count" => $count,
"items" => [],
];
foreach($albums as $album) {
$res = ["id" => $album->getId(), "name" => $album->getName()];
$arr["items"][] = $res;
}
$resolve($arr);
}
}

View file

@ -33,14 +33,26 @@ class Photos
return new Photo($photo);
}
function getEveryUserPhoto(User $user): \Traversable
function getEveryUserPhoto(User $user, int $page = 1, ?int $perPage = NULL): \Traversable
{
$perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE;
$photos = $this->photos->where([
"owner" => $user->getId()
]);
"owner" => $user->getId(),
"deleted" => 0
])->order("id DESC");
foreach($photos as $photo) {
foreach($photos->page($page, $perPage) as $photo) {
yield new Photo($photo);
}
}
function getUserPhotosCount(User $user)
{
$photos = $this->photos->where([
"owner" => $user->getId(),
"deleted" => 0
]);
return sizeof($photos);
}
}

View file

@ -2,7 +2,7 @@
namespace openvk\Web\Presenters;
use openvk\Web\Models\Entities\{Comment, Notifications\MentionNotification, Photo, Video, User, Topic, Post};
use openvk\Web\Models\Entities\Notifications\CommentNotification;
use openvk\Web\Models\Repositories\{Comments, Clubs, Videos};
use openvk\Web\Models\Repositories\{Comments, Clubs, Videos, Photos};
final class CommentPresenter extends OpenVKPresenter
{
@ -54,9 +54,6 @@ final class CommentPresenter extends OpenVKPresenter
if ($entity instanceof Post && $entity->getWallOwner()->isBanned())
$this->flashFail("err", tr("error"), tr("forbidden"));
if($_FILES["_vid_attachment"] && OPENVK_ROOT_CONF['openvk']['preferences']['videos']['disableUploading'])
$this->flashFail("err", tr("error"), tr("video_uploads_disabled"));
$flags = 0;
if($this->postParam("as_group") === "on" && !is_null($club) && $club->canBeModifiedBy($this->user->identity))
$flags |= 0b10000000;
@ -70,18 +67,22 @@ final class CommentPresenter extends OpenVKPresenter
}
}
# TODO move to trait
try {
$photo = NULL;
if($_FILES["_pic_attachment"]["error"] === UPLOAD_ERR_OK) {
$album = NULL;
if($wall > 0 && $wall === $this->user->id)
$album = (new Albums)->getUserWallAlbum($wallOwner);
$photo = Photo::fastMake($this->user->id, $this->postParam("text"), $_FILES["_pic_attachment"], $album);
$photos = [];
if(!empty($this->postParam("photos"))) {
$un = rtrim($this->postParam("photos"), ",");
$arr = explode(",", $un);
if(sizeof($arr) < 11) {
foreach($arr as $dat) {
$ids = explode("_", $dat);
$photo = (new Photos)->getByOwnerAndVID((int)$ids[0], (int)$ids[1]);
if(!$photo || $photo->isDeleted())
continue;
$photos[] = $photo;
}
}
} catch(ISE $ex) {
$this->flashFail("err", tr("error_when_publishing_comment"), tr("error_comment_file_too_big"));
}
$videos = [];
@ -103,7 +104,7 @@ final class CommentPresenter extends OpenVKPresenter
}
}
if(empty($this->postParam("text")) && !$photo && !$video)
if(empty($this->postParam("text")) && sizeof($photos) < 1 && sizeof($videos) < 1)
$this->flashFail("err", tr("error_when_publishing_comment"), tr("error_comment_empty"));
try {
@ -119,8 +120,8 @@ final class CommentPresenter extends OpenVKPresenter
$this->flashFail("err", tr("error_when_publishing_comment"), tr("error_comment_too_big"));
}
if(!is_null($photo))
$comment->attach($photo);
foreach($photos as $photo)
$comment->attach($photo);
if(sizeof($videos) > 0)
foreach($videos as $vid)

View file

@ -97,14 +97,13 @@ final class InternalAPIPresenter extends OpenVKPresenter
}
}
function renderGetPhotosFromPost(string $post_id) {
function renderGetPhotosFromPost(int $owner_id, int $post_id) {
if($_SERVER["REQUEST_METHOD"] !== "POST") {
header("HTTP/1.1 405 Method Not Allowed");
exit("иди нахуй заебал");
}
$id = explode("_", $post_id);
$post = (new Posts)->getPostById(intval($id[0]), intval($id[1]));
$post = (new Posts)->getPostById($owner_id, $post_id);
if(is_null($post)) {
$this->returnJson([

View file

@ -222,15 +222,20 @@ final class PhotosPresenter extends OpenVKPresenter
{
$this->assertUserLoggedIn();
$this->willExecuteWriteAction(true);
if(is_null($this->queryParam("album")))
$this->flashFail("err", tr("error"), tr("error_adding_to_deleted"), 500, true);
[$owner, $id] = explode("_", $this->queryParam("album"));
$album = $this->albums->get((int) $id);
if(is_null($this->queryParam("album"))) {
$album = $this->albums->getUserWallAlbum($this->user->identity);
} else {
[$owner, $id] = explode("_", $this->queryParam("album"));
$album = $this->albums->get((int) $id);
}
if(!$album)
$this->flashFail("err", tr("error"), tr("error_adding_to_deleted"), 500, true);
if(is_null($this->user) || !$album->canBeModifiedBy($this->user->identity))
# Для быстрой загрузки фоток из пикера фотографий нужен альбом, но юзер не может загружать фото
# в системные альбомы, так что так.
if(is_null($this->user) || !is_null($this->queryParam("album")) && !$album->canBeModifiedBy($this->user->identity))
$this->flashFail("err", tr("error_access_denied_short"), tr("error_access_denied"), 500, true);
if($_SERVER["REQUEST_METHOD"] === "POST") {
@ -261,6 +266,9 @@ final class PhotosPresenter extends OpenVKPresenter
$this->flashFail("err", tr("no_photo"), tr("select_file"), 500, true);
$photos = [];
if((int)$this->postParam("count") > 10)
$this->flashFail("err", tr("no_photo"), "ты еблан", 500, true);
for($i = 0; $i < $this->postParam("count"); $i++) {
try {
$photo = new Photo;

View file

@ -3,7 +3,7 @@ namespace openvk\Web\Presenters;
use openvk\Web\Models\Exceptions\TooMuchOptionsException;
use openvk\Web\Models\Entities\{Poll, Post, Photo, Video, Club, User};
use openvk\Web\Models\Entities\Notifications\{MentionNotification, RepostNotification, WallPostNotification};
use openvk\Web\Models\Repositories\{Posts, Users, Clubs, Albums, Notes, Videos, Comments};
use openvk\Web\Models\Repositories\{Posts, Users, Clubs, Albums, Notes, Videos, Comments, Photos};
use Chandler\Database\DatabaseConnection;
use Nette\InvalidStateException as ISE;
use Bhaktaraz\RSSGenerator\Item;
@ -249,35 +249,23 @@ final class WallPresenter extends OpenVKPresenter
if($this->postParam("force_sign") === "on")
$flags |= 0b01000000;
try {
$photos = [];
$video = NULL;
/* if($_FILES["_pic_attachment"]["error"] === UPLOAD_ERR_OK) {
$album = NULL;
if(!$anon && $wall > 0 && $wall === $this->user->id)
$album = (new Albums)->getUserWallAlbum($wallOwner);
$photo = Photo::fastMake($this->user->id, $this->postParam("text"), $_FILES["_pic_attachment"], $album, $anon);
} */
$photos = [];
$album = NULL;
if(!$anon && $wall > 0 && $wall === $this->user->id)
$album = (new Albums)->getUserWallAlbum($wallOwner);
if(!empty($this->postParam("photos"))) {
$un = rtrim($this->postParam("photos"), ",");
$arr = explode(",", $un);
foreach($_FILES as $fileId => $file) {
bdump([$fileId, $file, $file["error"] !== UPLOAD_ERR_OK, strncmp($fileId, "attachPic", 9) !== 0]);
if($file["error"] !== UPLOAD_ERR_OK || strncmp($fileId, "attachPic", 9) !== 0)
continue;
$photos[] = Photo::fastMake($this->user->id, $this->postParam("text"), $file, $album, $anon);
if(sizeof($arr) < 11) {
foreach($arr as $dat) {
$ids = explode("_", $dat);
$photo = (new Photos)->getByOwnerAndVID((int)$ids[0], (int)$ids[1]);
if(!$photo || $photo->isDeleted())
continue;
$photos[] = $photo;
}
}
/*if($_FILES["_vid_attachment"]["error"] === UPLOAD_ERR_OK)
$video = Video::fastMake($this->user->id, $_FILES["_vid_attachment"]["name"], $this->postParam("text"), $_FILES["_vid_attachment"], $anon);*/
} catch(\DomainException $ex) {
$this->flashFail("err", tr("failed_to_publish_post"), tr("media_file_corrupted"));
} catch(ISE $ex) {
$this->flashFail("err", tr("failed_to_publish_post"), tr("media_file_corrupted_or_too_large"));
}
try {
@ -324,7 +312,7 @@ final class WallPresenter extends OpenVKPresenter
}
}
if(empty($this->postParam("text")) && !$photos && sizeof($videos) < 1 && !$poll && !$note)
if(empty($this->postParam("text")) && sizeof($photos) < 1 && sizeof($videos) < 1 && !$poll && !$note)
$this->flashFail("err", tr("failed_to_publish_post"), tr("post_is_empty_or_too_big"));
try {

View file

@ -60,7 +60,7 @@
<input type="checkbox" name="as_group" /> {_comment_as_group}
</label>
</div>
<input type="file" class="postFileSel" id="postFilePic" name="_pic_attachment" accept="image/*" style="display:none;" />
<input type="hidden" name="photos" value="" />
<input type="hidden" name="videos" value="" />
<input type="hidden" name="poll" value="none" />
<input type="hidden" id="note" name="note" value="none" />
@ -77,7 +77,7 @@
<a class="header" href="javascript:toggleMenu({$textAreaId});">
{_attach}
</a>
<a href="javascript:void(document.querySelector('#post-buttons{$textAreaId} input[name=_pic_attachment]').click());">
<a id="photosAttachments">
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/mimetypes/application-x-egon.png" />
{_photo}
</a>
@ -109,6 +109,7 @@
});
u("#post-buttons{$textAreaId} input[name='videos']")["nodes"].at(0).value = ""
u("#post-buttons{$textAreaId} input[name='photos']")["nodes"].at(0).value = ""
</script>
{if $graffiti}

View file

@ -371,7 +371,7 @@ routes:
handler: "About->humansTxt"
- url: "/dev"
handler: "About->dev"
- url: "/iapi/getPhotosFromPost/{text}"
- url: "/iapi/getPhotosFromPost/{num}_{num}"
handler: "InternalAPI->getPhotosFromPost"
- url: "/tour"
handler: "About->tour"

View file

@ -92,8 +92,6 @@ u(".post-like-button").on("click", function(e) {
return false;
});
let picCount = 0;
function setupWallPostInputHandlers(id) {
/* u("#wall-post-input" + id).on("paste", function(e) {
if(e.clipboardData.files.length === 1) {
@ -117,25 +115,26 @@ function setupWallPostInputHandlers(id) {
});
u(`#wall-post-input${id}`).on("paste", function(e) {
let xhr = new XMLHttpRequest()
let formdat = new FormData()
let iterator = 0
for (let i = 0; i < e.clipboardData.files.length; i++) {
console.log(e.clipboardData.files[i]);
if(getMediaCount() >= 10) {
alert('Не больше 10 пикч');
}
if(e.clipboardData.files[i].type.match('^image/')) {
let blobURL = URL.createObjectURL(e.clipboardData.files[i]);
addPhotoMedia(e.clipboardData.files, blobURL, id);
addPhotoMedia(e.clipboardData.files[i])
}
}
});
u(`#post-buttons${id} input[name=_pic_attachment]`).on("change", function(e) {
let blobURL = URL.createObjectURL(e.target.files[0]);
addPhotoMedia(e.target.files, blobURL, id);
console.log(formdat)
});
function addPhotoMedia(files, preview, id) {
if(getMediaCount() >= 10) {
alert('Не больше 10 пикч');
} else {
picCount++;
u(`#post-buttons${id} .upload`).append(u(`
<div class="upload-item" id="aP${picCount}">
<a href="javascript:removePicture(${picCount})" class="upload-delete">×</a>
@ -190,7 +189,7 @@ function OpenMiniature(e, photo, post, photo_id) {
<center style="margin-bottom: 8pt;">
<div class="ovk-photo-slide-left"></div>
<div class="ovk-photo-slide-right"></div>
<img src="${photo}" style="max-width: 100%; max-height: 60vh;" id="ovk-photo-img">
<img src="${photo}" style="max-width: 100%; max-height: 60vh; user-select:none;" id="ovk-photo-img">
</center>
<div class="ovk-photo-details">
<img src="/assets/packages/static/openvk/img/loading_mini.gif">
@ -396,6 +395,7 @@ function addNote(textareaId, nid)
u("body").removeClass("dimmed");
u(".ovk-diag-cont").remove();
document.querySelector("html").style.overflowY = "scroll"
}
async function attachNote(id)
@ -711,3 +711,199 @@ $(document).on("click", "#editPost", (e) => {
text.style.display = "block"
}
})
// copypaste from videos picker
$(document).on("click", "#photosAttachments", async (e) => {
let body = `
<div class="topGrayBlock">
<div style="padding-top: 7px;padding-left: 12px;">
${tr("upload_new_photo")}:
<input type="file" multiple accept="image/*" id="fastFotosUplod" style="display:none">
<input type="button" class="button" value="${tr("upload_button")}" onclick="fastFotosUplod.click()">
<select id="albumSelect" style="width: 154px;float: right;margin-right: 17px;">
<option value="0">${tr("all_photos")}</option>
</select>
</div>
</div>
<div class="photosInsert" style="padding: 5px;height: 287px;overflow-y: scroll;">
<div style="position: fixed;z-index: 1007;width: 92%;background: white;margin-top: -5px;padding-top: 6px;"><h4>${tr("is_x_photos", 0)}</h4></div>
<div class="photosList album-flex" style="margin-top: 20px;"></div>
</div>
`
let form = e.currentTarget.closest("form")
MessageBox(tr("select_photo"), body, [tr("close")], [Function.noop]);
document.querySelector(".ovk-diag-body").style.padding = "0"
document.querySelector(".ovk-diag-cont").style.width = "630px"
document.querySelector(".ovk-diag-body").style.height = "335px"
async function insertPhotos(page, album = 0) {
let insertPlace = document.querySelector(".photosInsert .photosList")
document.querySelector(".photosInsert").insertAdjacentHTML("beforeend", `<img id="loader" style="max-height: 8px;max-width: 36px;" src="/assets/packages/static/openvk/img/loading_mini.gif">`)
let photos;
if(album == 0) {
photos = await API.Photos.getPhotos(page, 0)
} else {
photos = await API.Photos.getPhotos(page, Number(album))
}
document.querySelector(".photosInsert h4").innerHTML = tr("is_x_photos", photos.count)
console.log(photos)
let pagesCount = Math.ceil(Number(photos.count) / 24)
u("#loader").remove()
for(const photo of photos.items) {
let isAttached = (form.querySelector("input[name='photos']").value.includes(`${photo.owner_id}_${photo.id},`))
insertPlace.insertAdjacentHTML("beforeend", `
<div style="width: 14%;margin-bottom: 7px;margin-left: 13px;" class="album-photo" data-attachmentdata="${photo.owner_id}_${photo.id}" data-preview="${photo.photo_130}">
<a href="/photo${photo.owner_id}_${photo.id}">
<img class="album-photo--image" src="${photo.photo_130}" alt="..." style="${isAttached ? "background-color: #646464" : ""}">
</a>
</div>
`)
}
if(page < pagesCount) {
insertPlace.insertAdjacentHTML("beforeend", `
<div id="showMorePhotos" data-pagesCount="${pagesCount}" data-page="${page + 1}" style="width: 100%;text-align: center;background: #d5d5d5;height: 22px;padding-top: 9px;cursor:pointer;">
<span>more...</span>
</div>`)
}
}
insertPhotos(1)
let albums = await API.Photos.getAlbums()
for(const alb of albums.items) {
let sel = document.querySelector(".ovk-diag-body #albumSelect")
sel.insertAdjacentHTML("beforeend", `<option value="${alb.id}">${escapeHtml(alb.name)}</option>`)
}
$(".photosInsert").on("click", "#showMorePhotos", (e) => {
u(e.currentTarget).remove()
insertPhotos(Number(e.currentTarget.dataset.page))
})
$(".topGrayBlock #albumSelect").on("change", (evv) => {
document.querySelector(".photosInsert .photosList").innerHTML = ""
insertPhotos(1, evv.currentTarget.value)
})
function insertAttachment(id) {
let photos = form.querySelector("input[name='photos']")
if(!photos.value.includes(id + ",")) {
if(photos.value.split(",").length > 10) {
NewNotification(tr("error"), tr("max_attached_photos"))
return false
}
form.querySelector("input[name='photos']").value += (id + ",")
console.info(id + " attached")
return true
} else {
form.querySelector("input[name='photos']").value = form.querySelector("input[name='photos']").value.replace(id + ",", "")
console.info(id + " detached")
return false
}
}
$(".photosList").on("click", ".album-photo", (ev) => {
ev.preventDefault()
if(!insertAttachment(ev.currentTarget.dataset.attachmentdata)) {
u(form.querySelector(`.upload #aP[data-id='${ev.currentTarget.dataset.attachmentdata}']`)).remove()
ev.currentTarget.querySelector("img").style.backgroundColor = "white"
} else {
ev.currentTarget.querySelector("img").style.backgroundColor = "#646464"
let id = ev.currentTarget.dataset.attachmentdata
u(form.querySelector(`.upload`)).append(u(`
<div class="upload-item" id="aP" data-id="${ev.currentTarget.dataset.attachmentdata}">
<a class="upload-delete">×</a>
<img src="${ev.currentTarget.dataset.preview}">
</div>
`));
u(`.upload #aP[data-id='${ev.currentTarget.dataset.attachmentdata}'] .upload-delete`).on("click", () => {
form.querySelector("input[name='photos']").value = form.querySelector("input[name='photos']").value.replace(id + ",", "")
u(form.querySelector(`.upload #aP[data-id='${ev.currentTarget.dataset.attachmentdata}']`)).remove()
})
}
})
u("#fastFotosUplod").on("change", (evn) => {
let xhr = new XMLHttpRequest()
xhr.open("POST", "/photos/upload")
let formdata = new FormData()
let iterator = 0
for(const fille of evn.currentTarget.files) {
if(!fille.type.startsWith('image/')) {
continue;
}
if(fille.size > 5 * 1024 * 1024) {
continue;
}
if(evn.currentTarget.files.length >= 10) {
NewNotification(tr("error"), tr("max_attached_photos"))
return;
}
formdata.append("photo_"+iterator, fille)
iterator += 1
}
xhr.onloadstart = () => {
evn.currentTarget.parentNode.insertAdjacentHTML("beforeend", `<img id="loader" style="max-height: 8px;max-width: 36px;" src="/assets/packages/static/openvk/img/loading_mini.gif">`)
}
xhr.onload = () => {
let result = JSON.parse(xhr.responseText)
u("#loader").remove()
if(result.success) {
for(const pht of result.photos) {
let id = pht.owner + "_" + pht.vid
insertAttachment(id)
u(form.querySelector(`.upload`)).append(u(`
<div class="upload-item" id="aP" data-id="${pht.owner + "_" + pht.vid}">
<a class="upload-delete">×</a>
<img src="${pht.url}">
</div>
`));
u(`.upload #aP[data-id='${pht.owner + "_" + pht.vid}'] .upload-delete`).on("click", () => {
form.querySelector("input[name='photos']").value = form.querySelector("input[name='photos']").value.replace(id + ",", "")
u(form.querySelector(`.upload #aP[data-id='${id}']`)).remove()
})
}
u("body").removeClass("dimmed");
u(".ovk-diag-cont").remove();
document.querySelector("html").style.overflowY = "scroll"
}
}
formdata.append("hash", u("meta[name=csrf]").attr("value"))
formdata.append("count", iterator)
xhr.send(formdata)
})
})

View file

@ -412,6 +412,16 @@
"tip" = "Tip";
"tip_ctrl" = "to select multiple photos at once, hold down the Ctrl key when selecting files in Windows or the CMD key in Mac OS.";
"album_poster" = "Album poster";
"select_photo" = "Select photos";
"upload_new_photo" = "Upload new photo";
"is_x_photos_zero" = "Just zero photos.";
"is_x_photos_one" = "Just one photo.";
"is_x_photos_few" = "Just $1 photos.";
"is_x_photos_many" = "Just $1 photos.";
"is_x_photos_other" = "Just $1 photos.";
"all_photos" = "All photos";
/* Notes */
@ -697,6 +707,7 @@
"selecting_video" = "Selecting videos";
"upload_new_video" = "Upload new video";
"max_attached_videos" = "Max is 10 videos";
"max_attached_photos" = "Max is 10 photos";
"no_videos" = "You don't have uploaded videos.";
"no_videos_results" = "No results.";

View file

@ -394,6 +394,16 @@
"tip" = "Подсказка";
"tip_ctrl" = "для того, чтобы выбрать сразу несколько фотографий, удерживайте клавишу Ctrl при выборе файлов в ОС Windows или клавишу CMD в Mac OS.";
"album_poster" = "Обложка альбома";
"select_photo" = "Выберите фотографию";
"upload_new_photo" = "Загрузить новую фотографию";
"is_x_photos_zero" = "Всего ноль фотографий.";
"is_x_photos_one" = "Всего одна фотография.";
"is_x_photos_few" = "Всего $1 фотографии.";
"is_x_photos_many" = "Всего $1 фотографий.";
"is_x_photos_other" = "Всего $1 фотографий.";
"all_photos" = "Все фотографии";
/* Notes */
@ -656,6 +666,7 @@
"selecting_video" = "Выбор видеозаписей";
"upload_new_video" = "Загрузить новое видео";
"max_attached_videos" = "Максимум 10 видеозаписей";
"max_attached_photos" = "Максимум 10 фотографий";
"no_videos" = "У вас нет видео.";
"no_videos_results" = "Нет результатов.";