From e1381a67879b431f3d1d7d7024d0df7d33061aba Mon Sep 17 00:00:00 2001 From: Prithvijit <71138854+prithvijitguha@users.noreply.github.com> Date: Sat, 26 Feb 2022 21:26:58 +0530 Subject: [PATCH] =?UTF-8?q?=E2=9D=87=EF=B8=8F=20Added=20Infinite=20Scroll?= =?UTF-8?q?=20and=20Video=20API=20=20(#197)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :lipstick: style improvements to upload page * :necktie: Updated top_videos logic and video api * :sparkle: Added infinite scroll * :white_check_mark: Added tests --- .pylintrc | 3 +- crud/crud.py | 28 +++++++++++++- main.py | 44 ++++++++++++++++++++-- static/index.css | 4 +- static/index.js | 85 +++++++++++++++++++++++++++++++++++++++++++ static/upload.css | 20 ++++++++++ static/upload.js | 10 +---- templates/index.html | 10 ++--- templates/upload.html | 41 +++++++++------------ tests/test_main.py | 6 +++ 10 files changed, 208 insertions(+), 43 deletions(-) create mode 100644 static/upload.css diff --git a/.pylintrc b/.pylintrc index ec84468..408d643 100644 --- a/.pylintrc +++ b/.pylintrc @@ -85,7 +85,8 @@ disable=raw-checker-failed, suppressed-message, useless-suppression, deprecated-pragma, - use-symbolic-message-instead + use-symbolic-message-instead, + duplicate-code # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option diff --git a/crud/crud.py b/crud/crud.py index efb3f15..4c57afe 100644 --- a/crud/crud.py +++ b/crud/crud.py @@ -436,14 +436,20 @@ def get_video_link(db: Session, video_link: str): return db.query(models.Video).filter(models.Video.video_link == video_link).first() -def get_top_videos(db: Session): +def get_top_videos(db: Session, skip: int = 0, limit: int = 100): """ Get top videos ordered by views Gets the top 10 videos """ - return db.query(models.Video).order_by(models.Video.views.desc()).limit(10).all() + return ( + db.query(models.Video) + .order_by(models.Video.views.desc()) + .offset(skip) + .limit(limit) + .all() + ) # def delete video @@ -562,3 +568,21 @@ def delete_comment(db: Session, comment_id: int): except Exception as e: print(f"Could not delete user={comment_id}: {e}") return False + + +def get_profile_bool(db: Session, username: str): + """Gets profile picture flag + + Args: + db: Database + username: Username to check + + Returns: + Bool value of User.profile_picture + """ + return ( + db.query(models.User) + .filter(models.User.username == username) + .first() + .profile_picture + ) diff --git a/main.py b/main.py index 136c88a..010b55e 100644 --- a/main.py +++ b/main.py @@ -15,7 +15,7 @@ import os from html import escape -from typing import Optional +from typing import Optional, Union from tempfile import NamedTemporaryFile from fastapi import FastAPI, Request, Depends, Response, status @@ -42,6 +42,9 @@ origins = [ "http://localhost", "http://localhost:8080", + "http://localhost:8000", + "http://127.0.0.1:8000/", + "*", ] # static files directory for javascript and css @@ -110,6 +113,42 @@ def read_user(user_id: int, db: Session = Depends(get_db)): return db_user +@app.get("/get_top_videos/{skip}/{limit}") +def get_videos( + skip: Union[int, str] = 0, + limit: Union[int, str] = 10, + db: Session = Depends(get_db), +): + """Function to get top videos from database + + Args: + db: Database + skip: video of videos to start + limit: video of videos to end + + Returns: + Array of videos ordered by views + """ + return crud.get_top_videos(db, skip, limit) + + +@app.get("/get_profile_picture/{username}") +def get_profile_picture_bool( + username: str, + db: Session = Depends(get_db), +): + """Gets the profile picture bool flag + + Args: + username: Username to check + db: Database + + Returns: + boolean value of Bool value of User.profile_picture + """ + return crud.get_profile_bool(db, username) + + @app.get("/", response_class=HTMLResponse) async def home( request: Request, @@ -131,8 +170,7 @@ async def home( # sanitize active_user if active_user: active_user = utils.sanitize_active_user(active_user) - - top_videos = crud.get_top_videos(db) + top_videos = get_videos(db=db) thumbnail_drive = os.environ.get("thumbnail_drive") cloud_url = os.environ.get("cloud_url") thumbnail_url = f"{cloud_url}/{thumbnail_drive}" diff --git a/static/index.css b/static/index.css index c9c4bc2..51202d8 100644 --- a/static/index.css +++ b/static/index.css @@ -1,5 +1,7 @@ .profilePicture { border-radius: 50%; + width: 40px; + height: 40px; } .videoThumbnail { @@ -10,5 +12,5 @@ .videoThumbnail:hover { transform: scale(1.5); - transition-delay:0.9s; + transition-delay:0.5s; } \ No newline at end of file diff --git a/static/index.js b/static/index.js index e69de29..0347bc2 100644 --- a/static/index.js +++ b/static/index.js @@ -0,0 +1,85 @@ +var start = 11 +var end = 20 + +function append_new_videos(data_array) { + //for each element create a video element + data_array.forEach(create_video_element) +} + +function create_video_element(data) { + rows_html_collection = document.getElementsByClassName("row row-cols-5 mx-auto") + rows_array = Array.from(rows_html_collection) + //create class + main_div = document.createElement("div") + main_div.setAttribute("class", "mx-auto videoThumbnailContent"); + //create a href element child + ahref = document.createElement("a"); + ahref.href = `video/${data.video_link}`; + + thumbnail = document.createElement("img") + thumbnail.setAttribute("class", "img-fluid videoThumbnail") + thumbnail.setAttribute("src", `https://d32dcw9m3mntm7.cloudfront.net/thumbnail/${data.video_link}`) + //create image profile pic as child element + profile_pic = document.createElement("img") + profile_pic.setAttribute("class", "profilePicture") + profile_pic_flag = get_profile_pic_bool(data.video_username) + if (profile_pic_flag == "true") { + profile_pic.src = `https://d32dcw9m3mntm7.cloudfront.net/profile_picture/${data.video_username}` + } + else { + profile_pic.src = "./static/assets/default_picture.jpg" + } + //create video name + videoName = document.createElement("h6") + videoName.setAttribute("class", "videoName w-100") + videoName.innerHTML = data.video_name + //create video username + videoUsername = document.createElement("p") + videoUsername.setAttribute("class", "videoUsername") + videoUsername.innerHTML = data.video_username + // create view count + view_count = document.createElement("strong") + view_count.setAttribute("class", "viewCount") + view_count.innerHTML = `${data.views} views` + // create timestamp + timestamp = document.createElement("h6") + timestamp.setAttribute("class", "timestamp timestampVideo") + timestamp.innerHTML = moment(data.ts_upload).fromNow(); + ahref.appendChild(thumbnail) + main_div.appendChild(ahref) + main_div.appendChild(profile_pic) + main_div.appendChild(videoName) + main_div.appendChild(videoUsername) + main_div.appendChild(view_count) + main_div.appendChild(timestamp) + + + + //append it to rows + rows_array.slice(-1).pop().appendChild(main_div) +} + + +async function get_profile_pic_bool(username) { + let response = await fetch(`/get_profile_picture/${username}`); + let data = await response.text(); + return data; +} + + +function get_top_videos() { + fetch(`/get_top_videos/${start}/${end}`) + .then(response => response.json()) + .then(data => append_new_videos(data)); + start = start + end + end = end + end +} + +// If scrolled to bottom, load the next 10 videos +window.onscroll = () => { + if (window.innerHeight + window.scrollY >= document.body.offsetHeight) { + get_top_videos() + } +}; + + diff --git a/static/upload.css b/static/upload.css new file mode 100644 index 0000000..eeb5029 --- /dev/null +++ b/static/upload.css @@ -0,0 +1,20 @@ +.uploadContents { + width: 85%; + text-align: center; + padding-left: 25%; +} + +#demoVideo { + height: 45%; + width: 75%; +} + +#thumbnailImage { + height: 45%; + width: 75%; +} + +.formElements { + margin-top: 5%; + margin-bottom: 5%; +} \ No newline at end of file diff --git a/static/upload.js b/static/upload.js index 4f105a4..31a947e 100644 --- a/static/upload.js +++ b/static/upload.js @@ -52,9 +52,6 @@ function checkVideo(){ //change the frame element source and title $("#demoVideo").attr("src", URL.createObjectURL(video_file)) document.getElementById("demoVideo").play() - - //enable the capture thumbnail button - document.getElementById("thumbnailCapturebtn").disabled = false; //get video frame video = document.getElementById("demoVideo"); @@ -70,7 +67,7 @@ function checkVideo(){ } } - + /* function capture(){ //function to capture thumbnail canvas = document.getElementById('thumbnailDisplay'); @@ -81,6 +78,7 @@ function checkVideo(){ //add video as source for display document.getElementById("thumbnailImage").setAttribute("src", canvas_image) } + */ function checkImage(){ //get the element @@ -107,11 +105,7 @@ function checkVideo(){ } - - function get_metadata() { - //capture first image as thumbnail - capture() //autofill video name trimmed_name = video_file.name.split('.').slice(0, -1).join('.') //durationdocument.getElementById("inputVideoName").value = video_file.name; diff --git a/templates/index.html b/templates/index.html index ce12dcf..558f5cb 100644 --- a/templates/index.html +++ b/templates/index.html @@ -6,6 +6,7 @@ {% block content %} + @@ -13,21 +14,20 @@
{% for video in videos %} -
+
thumbnail -

{% if get_profile(video.video_username)%} profile-picture + alt="profile-picture" > {% else %} profile-picture + alt="profile-picture" > {% endif %}
{{ video.video_name }}

{{ video.video_username }}

{{ video.views }} views -
Uploaded {{ video.ts_upload }}
+
{{ video.ts_upload }}
{% endfor %}
diff --git a/templates/upload.html b/templates/upload.html index 555fd14..1be6ca3 100644 --- a/templates/upload.html +++ b/templates/upload.html @@ -4,67 +4,62 @@ {{ super() }} {% endblock %} {% block content %} + + + -
+
-

Video

- -
+

Video

+ +
- -
- -
-

Thumbnail

- +
+

Thumbnail

+
-
- - -
-

Save this thumbnail and upload it, if you like it!

-
+
-
+
-
+
-
+
-