Skip to content

Commit

Permalink
finished the search functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
Genadi888 committed Dec 31, 2023
1 parent 5f11f0c commit ac33d72
Show file tree
Hide file tree
Showing 15 changed files with 499 additions and 139 deletions.
60 changes: 55 additions & 5 deletions css/car-pictures.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ section {
}

.approval-btn {
margin-top: 29px;
/* margin-top: 29px; */
align-self: flex-start;
margin-left: 15px;
/* margin-left: 15px; */
}

#button-and-posts-separating-line {
Expand All @@ -29,13 +29,48 @@ section {
color: azure;
}

#aboveTopLineDiv {
display: flex;
width: 95%;
align-items: center;
justify-content: space-evenly;
margin-top: 16px;
}

#aboveTopLineDiv > form {
width: 50%;
margin: 0 !important;
}

#aboveTopLineDiv > form > input.form-control{
width: 50%;
outline: none !important;
box-shadow: unset !important;
}

#aboveTopLineDiv > form > .btn {
border-radius: 0px !important;
}

#aboveTopLineDiv > form > .btn:last-child {
border-top-right-radius: 3px !important;
border-bottom-right-radius: 3px !important;
}

/* TODO: Back4app remove post from Redis */

#aboveTopLineDiv > form > .btn-secondary{
border-top-left-radius: 0px !important;
border-bottom-left-radius: 0px !important;
}

section {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
align-items: center;

margin-top: 55px;
margin-top: 66px;
padding-bottom: 2%;
height: auto;
min-height: 100vh;
Expand All @@ -46,11 +81,17 @@ section {
--card-margin-top: 30px;
}

@media only screen and (min-width: 1440px) {
section {
margin-top: calc(4.4vw) !important;
}
}

#unapprovedPostsMessageContainer {
width: var(--card-width);
min-width: 250px;

margin-top: 34px;
/* margin-top: 34px; */
padding: 13px;

border: 5px dashed darkkhaki;
Expand All @@ -75,7 +116,7 @@ section {
}

.card {
margin-top: var(--card-margin-top) !important;
margin-top: 14px !important;
width: var(--card-width) !important;
min-width: 250px !important;
border: none !important;
Expand Down Expand Up @@ -1098,6 +1139,7 @@ ul.extra-comment-actions:focus-within {
user-select: none;
pointer-events: none;
justify-self: center;
text-align: center;
}

.loader {
Expand Down Expand Up @@ -1166,6 +1208,14 @@ ul.extra-comment-actions:focus-within {
}
}

#loader-wrapper {
width: 100%;
flex: 1;
display: flex;
justify-content: center;
align-items: center;
}

/* ? Custom scrollbar */
/* Firefox */
* {
Expand Down
62 changes: 59 additions & 3 deletions src/api/posts.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export async function* get2PostObjects() {

if (result.length == 0) {
//? If we didn't get any posts from server, return an empty array and signal the end of this generator.
return [];
return [];
} else {
yield result; //? Otherwise, yield the result and wait for the next call.
}
Expand All @@ -31,6 +31,38 @@ export async function* get2UnapprovedPostObjects() {
}
}

export async function* get2SearchedPostObjects(searchedPostsIds) {
for (let i = 0; i < searchedPostsIds.length; i += 2) {
const result = (await api.post('/functions/get2PostsByIds', { searchedPostsIds: JSON.stringify(searchedPostsIds.slice(i, i + 2)) })).result;

if (result.length == 0) {
//? If we didn't get any posts from server, return an empty array and signal the end of this generator.
return [];
} else if (result.length == 1) {
//? if we got the last post, signal the end of this generator.
return result;
} else {
yield result; //? Otherwise, yield the result and wait for the next call.
}
}
}

export async function* get2UnapprovedSearchedPostObjects(searchedPostsIds) {
for (let i = 0; i < searchedPostsIds.length; i += 2) {
const result = (await api.post('/functions/get2UnapprovedPostsByIds', { searchedPostsIds: JSON.stringify(searchedPostsIds.slice(i, i + 2)) })).result;

if (result.length == 0) {
//? If we didn't get any posts from server, return an empty array and signal the end of this generator.
return [];
} else if (result.length == 1) {
//? if we got the last post, signal the end of this generator.
return result;
} else {
yield result; //? Otherwise, yield the result and wait for the next call.
}
}
}

async function getUnapprovedPostsCount() {
return (await api.post('/functions/getUnapprovedPostsCount')).result
}
Expand Down Expand Up @@ -74,13 +106,37 @@ export async function likePost(postId) {
}

export async function unlikePost(postId, userId) {
const { objectId: likeObjId } =
(await api.get(`/PostsLikes?where={"postId": "${postId}",
const { objectId: likeObjId } =
(await api.get(`/PostsLikes?where={"postId": "${postId}",
"userWhoLiked": {"__type":"Pointer","className":"_User","objectId":"${userId}"}}`)).results[0];
return api.del(`/PostsLikes/${likeObjId}`);
}

export async function reportObject(report) {
addEntryWithUserPointer(report, 'reporter');
await api.post('/functions/reportObject', report);
}

export async function searchForPosts(srchText) {
try {
const response = await fetch("https://ex0tic-cars.netlify.app/.netlify/functions/search",
{ body: srchText, method: 'POST' });

if (!response.ok) {
const error = await response.json();
const err = new Error(error.error);
err.code = error.code;
throw err;
}

if (response.status == 204) {
return [];
} else {
const srchResultsArr = await response.json();
return srchResultsArr;
}
} catch (error) {
alert(error.message);
throw error;
}
}
17 changes: 14 additions & 3 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ import { sharePhotosView } from "./views/share-photos.js";
import { showroomsView } from "./views/showrooms.js";
import { verifyView } from "./views/verify.js";

sessionStorage.removeItem('popstateChanges');

window.addEventListener('popstate', () => {
let popstateChanges = Number(sessionStorage.getItem('popstateChanges') || 0);
sessionStorage.setItem('popstateChanges', ++popstateChanges);
});

const main = document.querySelector('#main');
const root = main.attachShadow({ mode: 'open' });

Expand All @@ -32,14 +39,18 @@ page((ctx, next) => {
page('/index.html', '/');
page('/', homeView);
page('/car-pictures', carPicturesView);
page.exit('/car-pictures', (ctx, next) => {
ctx.carPicturesController?.abort();
next();
});
page('/about-us', aboutUsView);
page('/showrooms', showroomsView);
page('/share-photos', sharePhotosView);
page('/share-photos:id', editPostView);

page.exit('/share-photos', exitFunction);
page.exit('/share-photos:id', exitFunction);
function exitFunction(ctx, next) {
page.exit('/share-photos', sharePhotosExitFunction);
page.exit('/share-photos:id', sharePhotosExitFunction);
function sharePhotosExitFunction(ctx, next) {
ctx.controller.abort(); //? remove navbar's listener for click event after user leaves "share-photos"
sessionStorage.removeItem('userHasUnsavedData');
next();
Expand Down
99 changes: 26 additions & 73 deletions src/views/car-pictures/car-pictures.js
Original file line number Diff line number Diff line change
@@ -1,89 +1,42 @@
import { html } from "../../lib/lit-html.js";
import { until } from "../../lib/directives/until.js";
import { get2PostObjects, get2UnapprovedPostObjects } from "../../api/posts.js";
import { sectionClickHandler } from "./infoWindow/infoWindow.js";
import { carPicturesTemplate } from "./carPicturesTemplate.js";
import { getSwitchToApprovalModeHandler } from "./posts/switchToApprovalModeHandler.js";
import { getNoPostsTemplate } from "./posts/getNoPostsTemplate.js";
import { getUnapprovedPostsMessageTemplate } from "./posts/unapprovedPostsMessageTemplate.js";
import { getSectionContentTemplate } from "./posts/sectionContentTemplate.js";
import { setUpMiscStuff } from "./posts/setUpMiscStuff.js";
import { startObservingTheThirdLastCard } from "./posts/startObservingTheThirdLastCard.js";
import { renderNew, resetState } from "./renderNew.js";

export async function carPicturesView(ctx) {
const loadingTemplate = () => html`
<h1 id="loading">Loading posts<div class="loader"></div>
</h1>
`
const posts = [];
const generatorsObject = {
asyncPostsGenerator: get2PostObjects(),
asyncPostsGeneratorIsDone: false,
asyncUnapprovedPostsGenerator: get2UnapprovedPostObjects(),
asyncUnapprovedPostsGeneratorIsDone: false,
if (!ctx.user?.isModerator && ctx.hash == 'approval-mode') {
ctx.page.redirect('/car-pictures');
return;
}
let miscStuffSetUp = false;
let portionsRendered = 0;

async function renderNew() {
if (generatorsObject.asyncPostsGeneratorIsDone
|| generatorsObject.asyncUnapprovedPostsGeneratorIsDone) {
//? If one of the generators is exhausted, don't bother rendering anything.
return;
}

const sectionContentPromise = getSectionContentTemplate(
getNoPostsTemplate,
ctx.hash == 'approval-mode' ? 'unapproved' : null,
posts,
generatorsObject,
ctx,
);
const posts = [];
resetState(posts);

if (posts.length > 0) {
//? If we already have 2 posts, the displaying of the "loading" template is avoided and the posts are shown after they have loaded.
await sectionContentPromise;
}
ctx.carPicturesController = new AbortController();
window.addEventListener('popstate', () => {
resetState(posts, ctx.user?.isModerator);

/*
? After awaiting the sectionContentPromise and resuming the execution of this async function,
? we check if the page's hash is different from the hash in the "ctx" object
? (the post mode has been switched to something else
? while the sectionContentPromise was still in "pending" state).
? If this is the case we won't bother rendering anything into the section.
*/
if (window.location.hash.slice(1) !== ctx.hash) {
return;
}
const srchRegex = /(?<=search=)(?<srchStr>.*?)(?=&|$)/m;

ctx.render(
carPicturesTemplate(
ev => sectionClickHandler(ev, posts, ctx),
until(sectionContentPromise, loadingTemplate()),
ctx.user?.isModerator ? getSwitchToApprovalModeHandler(ctx) : null,
ctx.user ? until(getUnapprovedPostsMessageTemplate(sectionContentPromise), null) : null,
ctx,
)
);
const queryStr = window.location.search.slice(1);

if (!miscStuffSetUp) {
setUpMiscStuff(ctx, sectionContentPromise);
miscStuffSetUp = true;
let urlSrchText = null; //? this text will be used in renderNew for auto search when first rendering
if (queryStr) {
urlSrchText = srchRegex.exec(decodeURIComponent(queryStr)).groups.srchStr.trim();
}

await sectionContentPromise; //? we wait for the two cards to show up on the screen
//? (in getSectionContentTemplate) the window path may change while some post promises are still pending so we create this variable to prevent the rendering of posts in a wrong view
let windowPath = window.location.href.replace(window.location.origin, '');
windowPath = window.location.hash ? windowPath : windowPath.replace("#", '');

renderNew(ctx, posts, urlSrchText ? [] : null, urlSrchText || null, windowPath);
}, { signal: ctx.carPicturesController.signal });

portionsRendered++;
if (posts.length == 0) { //? If there are no posts, start rendering them.
const srchRegex = /(?<=search=)(?<srchStr>.*?)(?=&|$)/m;

if (portionsRendered < 2) {
renderNew(); //? render one more portion
} else {
portionsRendered = 0;
startObservingTheThirdLastCard(ctx, renderNew); //? When user sees the third card, repeat the rendering cycle.
let urlSrchText = null; //? this text will be used in renderNew for auto search when first rendering
if (ctx.querystring) {
urlSrchText = srchRegex.exec(decodeURIComponent(ctx.querystring)).groups.srchStr.trim();
}
}

if (posts.length == 0) { //? If there are no posts, start rendering them.
renderNew();
renderNew(ctx, posts, urlSrchText ? [] : null, urlSrchText || null);
}
}
28 changes: 20 additions & 8 deletions src/views/car-pictures/carPicturesTemplate.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
import { html } from "../../lib/lit-html.js";

export const carPicturesTemplate = (sectionClickHandler, postsTemplate, onSwitchToApprovalMode, unapprovedPostsMessageTemplatePromise, ctx) => html`
export const carPicturesTemplate = (sectionClickHandler, postsTemplate, onSwitchToApprovalMode, unapprovedPostsMessageTemplatePromise, searchHandler, ctx, searchMode) => html`
<link rel="stylesheet" href="/css/car-pictures.css">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<a id="go-to-top-link"><img src="../../images/arrow-up-circle.svg" alt="" srcset=""></a>
<section @click=${sectionClickHandler}>
${
onSwitchToApprovalMode ?
html`
<button @click=${onSwitchToApprovalMode} type="button" class="approval-btn btn btn-primary">Switch to ${ctx.hash == 'approval-mode' ? 'normal' : 'approval'} mode</button>
<hr id="button-and-posts-separating-line">
` : null
}
<div id="aboveTopLineDiv">
${
onSwitchToApprovalMode ?
html`
<button @click=${onSwitchToApprovalMode} type="button" class="approval-btn btn btn-primary">Switch to ${ctx.hash == 'approval-mode' ? 'normal' : 'approval'} mode</button>
` : null
}
<form @submit=${searchHandler} class="input-group mb-3">
<input id="postsSearchInput" type="text" class="form-control" placeholder="Search for a title, extra info, owner name ..." aria-label="Recipient's username" aria-describedby="button-addon2">
<button class="btn btn-primary" type="submit" id="button-addon2">Search</button>
${
searchMode ?
html`<a href="/car-pictures${window.location.hash}" class="btn btn-secondary" type="submit">Go back</a>`
: null
}
</form>
</div>
<hr id="button-and-posts-separating-line">
${unapprovedPostsMessageTemplatePromise}
${postsTemplate}
</section>
Expand Down
Loading

0 comments on commit ac33d72

Please sign in to comment.