UI/UX improvements and code refactoring

This commit is contained in:
sairaj mote 2023-01-13 02:57:26 +05:30
parent ca201b4827
commit 02ab002583
4 changed files with 327 additions and 353 deletions

View File

@ -322,6 +322,22 @@ ul {
width: 100%;
}
.margin-left-0-3 {
margin-left: 0.3rem;
}
.margin-left-0-5 {
margin-left: 0.5rem;
}
.margin-right-0-3 {
margin-right: 0.3rem;
}
.margin-right-0-5 {
margin-right: 0.5rem;
}
.ripple {
position: absolute;
border-radius: 50%;
@ -389,6 +405,23 @@ ul {
justify-items: center;
}
#logo {
grid-area: logo;
color: inherit;
}
theme-toggle {
grid-area: theme;
justify-self: end;
align-self: center;
}
#search_wrapper {
grid-area: search;
width: 100%;
position: relative;
}
#main_header {
position: relative;
display: grid;
@ -396,39 +429,23 @@ ul {
padding: 1rem;
align-items: center;
grid-template-columns: 1fr auto auto;
grid-template-areas: "logo info theme" "search search search";
}
#main_header a.button {
grid-area: info;
font-size: 0.9rem;
font-weight: 500;
border: solid thin var(--accent-color);
}
.header__company-name {
grid-area: logo;
font-weight: 500;
}
#main_header__logo {
height: 1.8rem;
width: 1.8rem;
}
.theme-switcher {
position: relative;
justify-self: flex-end;
width: 1.5rem;
height: 1.5rem;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
}
.theme-switcher .icon {
position: absolute;
transition: transform 0.6s;
}
.theme-switcher__checkbox {
display: none;
}
.theme-switcher__checkbox:checked ~ .moon-icon {
transform: scale(0) rotate(90deg);
}
.theme-switcher__checkbox:not(:checked) ~ .sun-icon {
transform: scale(0) rotate(-90deg);
height: 1.3rem;
width: 1.3rem;
}
.page {
@ -439,14 +456,6 @@ ul {
font-size: 2rem;
}
#search_section {
position: relative;
display: grid;
gap: 0.5rem 0;
padding: 4rem 0;
justify-items: center;
}
.app-icon {
height: 3rem;
width: 3rem;
@ -480,27 +489,35 @@ ul {
stroke-dashoffset: 0;
}
}
.app-name {
font-weight: 500;
margin-bottom: 1rem;
font-size: 0.9rem;
color: rgba(var(--text-color), 0.7);
}
.search-container {
position: relative;
margin-bottom: 1rem;
width: min(28rem, 100%);
}
.search-torrent {
--border-radius: 2rem;
flex: 1;
width: 100%;
--border-radius: 0.5rem;
--background: rgba(var(--text-color), 0.06);
--padding: 0 0.3rem 0 0.8rem;
}
.search-torrent .icon {
fill: rgba(var(--text-color), 0.7);
}
#filter_button {
position: relative;
border-radius: 0.5rem;
}
#filter_button[data-active]::after {
display: flex;
content: attr(data-active);
position: absolute;
top: 0;
right: 0;
padding: 0.1em 0.3rem;
background-color: var(--accent-color);
color: rgba(var(--background-color), 1);
border-radius: 0.5rem;
font-size: 0.8rem;
font-weight: 500;
}
.search-suggestions-container {
top: 100%;
position: absolute;
@ -726,19 +743,21 @@ sm-option {
}
.option-selector {
display: flex;
flex-wrap: wrap;
gap: 0.8rem;
padding: 1rem 0;
}
.filter-option {
display: inline-flex;
margin: 0 0.5rem 0.8rem 0;
-webkit-tap-highlight-color: transparent;
}
.filter-option input {
display: none;
}
.filter-option input:checked ~ .option-text {
color: rgba(255, 255, 255, 0.9);
color: rgba(var(--background-color), 1);
background-color: var(--accent-color);
border: solid var(--accent-color) thin;
}
@ -757,28 +776,30 @@ sm-option {
}
#filters_bar {
display: grid;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 1rem;
margin-top: 1rem;
align-items: flex-start;
grid-template-columns: minmax(0, 1fr) auto;
}
#selected_filters_container {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.selected-filter {
display: flex;
align-items: center;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 0.3rem;
padding: 0.4rem 0.5rem;
margin: 0 0.5rem 0.8rem 0;
border: solid rgba(var(--text-color), 0.2) thin;
border-radius: 0.3rem;
}
.selected-filter .icon {
width: 1.2rem;
}
#page_selector sm-chip,
#search_page_selector sm-chip {
@ -843,7 +864,14 @@ sm-option {
grid-template-columns: 1fr 90vw 1fr;
}
#main_header {
padding: 1.5rem;
padding: 1rem 1.5rem;
grid-template-areas: "logo info theme";
}
#search_wrapper {
grid-area: 1/1/2/-1;
margin: 0 auto;
width: 28rem;
max-width: calc(100% - 22rem);
}
.page__title {
font-size: 3rem;

2
css/main.min.css vendored

File diff suppressed because one or more lines are too long

View File

@ -269,6 +269,18 @@ ul {
.w-100 {
width: 100%;
}
.margin-left-0-3 {
margin-left: 0.3rem;
}
.margin-left-0-5 {
margin-left: 0.5rem;
}
.margin-right-0-3 {
margin-right: 0.3rem;
}
.margin-right-0-5 {
margin-right: 0.5rem;
}
.ripple {
position: absolute;
border-radius: 50%;
@ -331,6 +343,20 @@ ul {
place-content: center;
justify-items: center;
}
#logo {
grid-area: logo;
color: inherit;
}
theme-toggle {
grid-area: theme;
justify-self: end;
align-self: center;
}
#search_wrapper {
grid-area: search;
width: 100%;
position: relative;
}
#main_header {
position: relative;
@ -339,37 +365,23 @@ ul {
padding: 1rem;
align-items: center;
grid-template-columns: 1fr auto auto;
grid-template-areas:
"logo info theme"
"search search search";
a.button {
grid-area: info;
font-size: 0.9rem;
font-weight: 500;
border: solid thin var(--accent-color);
}
}
.header__company-name {
grid-area: logo;
font-weight: 500;
}
#main_header__logo {
height: 1.8rem;
width: 1.8rem;
}
.theme-switcher {
position: relative;
justify-self: flex-end;
width: 1.5rem;
height: 1.5rem;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
.icon {
position: absolute;
transition: transform 0.6s;
}
}
.theme-switcher__checkbox {
display: none;
&:checked ~ .moon-icon {
transform: scale(0) rotate(90deg);
}
&:not(:checked) ~ .sun-icon {
transform: scale(0) rotate(-90deg);
}
height: 1.3rem;
width: 1.3rem;
}
.page {
@ -378,14 +390,6 @@ ul {
.page__title {
font-size: 2rem;
}
#search_section {
position: relative;
display: grid;
gap: 0.5rem 0;
padding: 4rem 0;
justify-items: center;
}
.app-icon {
height: 3rem;
width: 3rem;
@ -408,24 +412,33 @@ ul {
stroke-dashoffset: 0;
}
}
.app-name {
font-weight: 500;
margin-bottom: 1rem;
font-size: 0.9rem;
color: rgba(var(--text-color), 0.7);
}
.search-container {
position: relative;
margin-bottom: 1rem;
width: min(28rem, 100%);
}
.search-torrent {
--border-radius: 2rem;
flex: 1;
width: 100%;
--border-radius: 0.5rem;
--background: rgba(var(--text-color), 0.06);
--padding: 0 0.3rem 0 0.8rem;
.icon {
fill: rgba(var(--text-color), 0.7);
}
}
#filter_button {
position: relative;
border-radius: 0.5rem;
&[data-active]::after {
display: flex;
content: attr(data-active);
position: absolute;
top: 0;
right: 0;
padding: 0.1em 0.3rem;
background-color: var(--accent-color);
color: rgba(var(--background-color), 1);
border-radius: 0.5rem;
font-size: 0.8rem;
font-weight: 500;
}
}
.search-suggestions-container {
top: 100%;
position: absolute;
@ -644,17 +657,19 @@ sm-option {
}
.option-selector {
display: flex;
flex-wrap: wrap;
gap: 0.8rem;
padding: 1rem 0;
}
.filter-option {
display: inline-flex;
margin: 0 0.5rem 0.8rem 0;
-webkit-tap-highlight-color: transparent;
input {
display: none;
}
input:checked ~ .option-text {
color: rgba(255, 255, 255, 0.9);
color: rgba(var(--background-color), 1);
background-color: var(--accent-color);
border: solid var(--accent-color) thin;
}
@ -671,24 +686,25 @@ sm-option {
}
}
#filters_bar {
display: grid;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 1rem;
margin-top: 1rem;
align-items: flex-start;
grid-template-columns: minmax(0, 1fr) auto;
}
#selected_filters_container {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.selected-filter {
user-select: none;
cursor: pointer;
display: inline-flex;
display: flex;
align-items: center;
user-select: none;
gap: 0.3rem;
padding: 0.4rem 0.5rem;
margin: 0 0.5rem 0.8rem 0;
border: solid rgba(var(--text-color), 0.2) thin;
border-radius: 0.3rem;
.icon {
width: 1.2rem;
}
}
#page_selector,
@ -755,7 +771,14 @@ sm-option {
grid-template-columns: 1fr 90vw 1fr;
}
#main_header {
padding: 1.5rem;
padding: 1rem 1.5rem;
grid-template-areas: "logo info theme";
}
#search_wrapper {
grid-area: 1/1/2/-1;
margin: 0 auto;
width: 28rem;
max-width: calc(100% - 22rem);
}
.page__title {
font-size: 3rem;

View File

@ -15,6 +15,7 @@
<script src="https://cdn.jsdelivr.net/npm/fuse.js@6.4.6" defer></script>
<script src="scripts/lib.js" defer></script>
<script src="scripts/btcOperator.js" defer></script>
<script src="https://unpkg.com/uhtml@3.0.1/es.js"></script>
</head>
<body data-theme="">
@ -29,7 +30,7 @@
</sm-popup>
<sm-popup id="filter_popup">
<header class="popup__header" slot="header">
<button class="popup__header__close" onclick="getRef('filter_popup').hide()">
<button class="popup__header__close" onclick="closePopup()">
<svg class="icon close-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
@ -46,19 +47,39 @@
<section id="tags_selector" class="option-selector"></section>
<div class="flex gap-1">
<button class="justify-right" onclick="resetFilter()">Clear</button>
<button onclick="addFilter()" class="button button--primary">Fliter</button>
<button onclick="addFilter()" class="button button--primary">Filter</button>
</div>
</form>
</section>
</sm-popup>
<header id="main_header">
<div class="flex align-center">
<a href="#/home" class="flex align-center header__company-name">
<svg id="main_header__logo" class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
d="M20.46,21.32C20,19.78,18.6,18.59,15.3,17a12.67,12.67,0,0,1-2.64-1.56,4.27,4.27,0,0,1-.79-1,2.6,2.6,0,0,1,0-1.41c.24-.68.49-1,2.43-2.85a7.18,7.18,0,0,0,2.09-2.92,4.25,4.25,0,0,0,0-1.77,6.52,6.52,0,0,0-2.85-3.11c-.56-.36-.81-.4-.81-.15a2.33,2.33,0,0,1-.18.45L12.4,3l-.53-.36c-.28-.21-.64-.41-.77-.49s-.46-.11-.46,0a6.21,6.21,0,0,1-.37.83s-.08,0-.17-.08c-1.15-.83-1.64-1-1.64-.73A7.33,7.33,0,0,1,7.7,3.65C6.48,5.68,5.24,6.7,4,6.7c-.56,0-.54,0-.37.64s.2.58.68.43a3.37,3.37,0,0,0,1.09-.54.86.86,0,0,1,.3-.17,1.34,1.34,0,0,1,.13.39.79.79,0,0,0,.17.4A3.5,3.5,0,0,0,7.37,7.3L7.8,7l.09.34c.12.45.19.51.62.39a4.25,4.25,0,0,0,2.17-1.54l.38-.45,0,.39A5.92,5.92,0,0,1,8.89,9.54L7.67,10.71c-2,1.93-1.89,3.51.37,5a27.41,27.41,0,0,0,2.89,1.51c.17.07.62.32,1,.54C14,19,15,20.23,15,21.48a2,2,0,0,0,0,.49h0c0,.05,0,.05.56-.1a1.89,1.89,0,0,0,.53-.21,2.41,2.41,0,0,0-.34-1.15,7.05,7.05,0,0,0-1.68-1.77,21.91,21.91,0,0,0-3.2-1.83A9.53,9.53,0,0,1,8.16,15.2a2.18,2.18,0,0,1-.74-1.55C7.42,12.79,7.86,12,9,11c1.77-1.64,2.45-2.45,2.92-3.55a2.28,2.28,0,0,0,.26-1.26A2,2,0,0,0,12,5.06l-.2-.45L12,4.3l.28-.49.09-.18L12.6,4a3.69,3.69,0,0,1,.61,1.76A3.47,3.47,0,0,1,12.94,7l-.09.25s-.21.37-.41.69A17.78,17.78,0,0,1,9.91,10.6c-1.07,1-1.43,1.62-1.47,2.47a2.05,2.05,0,0,0,.7,1.73,10.47,10.47,0,0,0,3.28,2.08c2.28,1.13,3.26,1.81,4,2.73a2.94,2.94,0,0,1,.74,1.75,1.26,1.26,0,0,0,.09.57.48.48,0,0,0,.26,0l.51-.13.29-.08,0-.28c-.13-1-1-2-2.47-3a25.52,25.52,0,0,0-3.26-1.77,8.59,8.59,0,0,1-2.23-1.43,2.09,2.09,0,0,1-.5-2.62c.26-.53.5-.83,2.35-2.6,1.51-1.45,2.15-2.58,2.15-3.79A3.67,3.67,0,0,0,13,3.48a3,3,0,0,1-.4-.42A1.85,1.85,0,0,1,13,2.33a6.74,6.74,0,0,1,1.83,1.73,2.62,2.62,0,0,1,.47,1.68,3,3,0,0,1-.55,1.84c-.45.78-.79,1.14-2.67,2.93a5.56,5.56,0,0,0-1.3,1.64,1.77,1.77,0,0,0-.21,1,1.76,1.76,0,0,0,.19.92,6.28,6.28,0,0,0,2.9,2.34,21.6,21.6,0,0,1,3.66,2c1.35,1,2,2,2,3a1.06,1.06,0,0,0,.05.47,2.83,2.83,0,0,0,1-.24C20.56,21.68,20.56,21.66,20.46,21.32ZM7.29,6.4h0a2.23,2.23,0,0,1-.9.28L6,6.72l.43-.53a15.22,15.22,0,0,0,1.89-3,3.52,3.52,0,0,1,.38-.67c.07-.08.49.2,1,.64l.39.35L9.66,4A6.7,6.7,0,0,1,7.29,6.4Zm3.58-1.11A5.8,5.8,0,0,1,9.25,6.51h0a3.3,3.3,0,0,1-.74.17l-.35,0,.39-.49a15.64,15.64,0,0,0,1.32-2,4.63,4.63,0,0,1,.28-.49c.06-.08.33.26.57.77l.28.57Zm1-1.4a1.63,1.63,0,0,1-.28.4A6.63,6.63,0,0,1,11,3.72l-.53-.56.12-.29c.2-.49.24-.51.64-.19a5.57,5.57,0,0,1,.85.78A2.78,2.78,0,0,1,11.87,3.89Z" />
</svg>
<a href="#/home" class="header__company-name">RanchiMall</a>
</div>
FLO Torrent
</a>
<section id="search_wrapper">
<sm-input id="search_torrent" class="search-torrent" type="search" placeholder="Search">
<svg class="icon" slot="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<path
d="M54.69,14.28l8.83-8.83a1.62,1.62,0,0,0-1.14-2.77H21.15A1.63,1.63,0,0,0,19.53,4.3V59.7a1.62,1.62,0,0,0,2.78,1.13l8.83-9a1.62,1.62,0,0,0,.46-1.14V38.06a1.58,1.58,0,0,1,.48-1.14l8.81-8.83a1.62,1.62,0,0,0-1.15-2.77H33.22A1.62,1.62,0,0,1,31.6,23.7V16.37a1.63,1.63,0,0,1,1.62-1.62H53.55A1.63,1.63,0,0,0,54.69,14.28Z" />
<path
d="M1.62,14.75H12.36A1.62,1.62,0,0,0,14,13.13V4.3a1.63,1.63,0,0,0-1.62-1.62H7.47a1.6,1.6,0,0,0-1.35.73L.27,12.24A1.62,1.62,0,0,0,1.62,14.75Z" />
</svg>
<button id="filter_button" class="button icon-only" slot="right"
onclick="getRef('filter_popup').show()">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px"
fill="#000000">
<path d="M0 0h24v24H0z" fill="none" />
<path
d="M3 17v2h6v-2H3zM3 5v2h10V5H3zm10 16v-2h8v-2h-8v-2h-2v6h2zM7 9v2H3v2h4v2h2V9H7zm14 4v-2H11v2h10zm-6-4h2V7h4V5h-4V3h-2v6z" />
</svg>
</button>
</sm-input>
<section id="search_suggestions" class="search-suggestions-container"></section>
</section>
<a href="#/how_it_works" class="button justify-right">How it works?</a>
<theme-toggle></theme-toggle>
</header>
@ -74,26 +95,6 @@
<p>Getting torrents from FLO blockchain</p>
</section>
<section id="home" class="page hidden">
<section id="search_section" class="page-layout">
<svg class="app-icon icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<path
d="M54.69,14.28l8.83-8.83a1.62,1.62,0,0,0-1.14-2.77H21.15A1.63,1.63,0,0,0,19.53,4.3V59.7a1.62,1.62,0,0,0,2.78,1.13l8.83-9a1.62,1.62,0,0,0,.46-1.14V38.06a1.58,1.58,0,0,1,.48-1.14l8.81-8.83a1.62,1.62,0,0,0-1.15-2.77H33.22A1.62,1.62,0,0,1,31.6,23.7V16.37a1.63,1.63,0,0,1,1.62-1.62H53.55A1.63,1.63,0,0,0,54.69,14.28Z" />
<path
d="M1.62,14.75H12.36A1.62,1.62,0,0,0,14,13.13V4.3a1.63,1.63,0,0,0-1.62-1.62H7.47a1.6,1.6,0,0,0-1.35.73L.27,12.24A1.62,1.62,0,0,0,1.62,14.75Z" />
</svg>
<h4 class="app-name">FLO Torrent</h4>
<section class="search-container">
<sm-input id="search_torrent" class="search-torrent" type="search" placeholder="Search">
<svg class="icon" slot="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
d="M18.031 16.617l4.283 4.282-1.415 1.415-4.282-4.283A8.96 8.96 0 0 1 11 20c-4.968 0-9-4.032-9-9s4.032-9 9-9 9 4.032 9 9a8.96 8.96 0 0 1-1.969 5.617zm-2.006-.742A6.977 6.977 0 0 0 18 11c0-3.868-3.133-7-7-7-3.868 0-7 3.132-7 7 0 3.867 3.132 7 7 7a6.977 6.977 0 0 0 4.875-1.975l.15-.15z" />
</svg>
</sm-input>
<section id="search_suggestions" class="search-suggestions-container"></section>
</section>
</section>
<section class="page-layout">
<div class="flex align-center space-between">
<h4>Recent</h4>
@ -134,36 +135,12 @@
</section>
</section>
<section id="search" class="page hidden page-layout">
<div class="grid gap-1">
<h1 class="page__title">Search</h1>
<section class="search-container">
<sm-input id="advance_torrent_search" class="search-torrent" type="search">
<svg class="icon" slot="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
d="M18.031 16.617l4.283 4.282-1.415 1.415-4.282-4.283A8.96 8.96 0 0 1 11 20c-4.968 0-9-4.032-9-9s4.032-9 9-9 9 4.032 9 9a8.96 8.96 0 0 1-1.969 5.617zm-2.006-.742A6.977 6.977 0 0 0 18 11c0-3.868-3.133-7-7-7-3.868 0-7 3.132-7 7 0 3.867 3.132 7 7 7a6.977 6.977 0 0 0 4.875-1.975l.15-.15z" />
</svg>
</sm-input>
<section id="advance_search_suggestions" class="search-suggestions-container"></section>
</section>
</div>
<div class="flex align-center space-between">
<p id="result_for"></p>
<button id="filter_button" class="button" onclick="getRef('filter_popup').show()">
<svg class="icon button__icon button__icon--left" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" width="24" height="24">
<path fill="none" d="M0 0H24V24H0z" />
<path d="M21 4L21 6 20 6 14 15 14 22 10 22 10 15 4 6 3 6 3 4z" />
</svg>
Filter
</button>
</div>
<p id="result_for"></p>
<section id="filters_bar" class="hidden">
<div id="selected_filters_container"></div>
<button class="button" onclick="resetFilter()">
<svg class="icon button__icon button__icon--left" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" width="24" height="24">
<svg class="icon margin-right-0-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm0-11.414L9.172 7.757 7.757 9.172 10.586 12l-2.829 2.828 1.415 1.415L12 13.414l2.828 2.829 1.415-1.415L13.414 12l2.829-2.828-1.415-1.415L12 10.586z" />
@ -179,6 +156,7 @@
<h1 class="page__title">Browse</h1>
<sm-chips id="browse_category_selector">
<sm-chip value="movie" selected>Movie</sm-chip>
<sm-chip value="tv series">Series</sm-chip>
</sm-chips>
</section>
<section id="browser_category_torrents" class="torrent-container"></section>
@ -308,18 +286,6 @@
<footer id="main_footer" class="page-layout">
<p>Powered by FLO blockchain</p>
</footer>
<template id="torrent_card_template">
<li class="torrent-card">
<a class="torrent-info grid">
<div class="torrent-card__icon torrent-type-icon"></div>
<h3 class="torrent-card__title"></h3>
<span class="torrent-card__uploader"></span>
</a>
<button class="torrent-card__download-button">
Get Torrent
</button>
</li>
</template>
<script>
const cryptocoin = "FLO";
const mainnet = `https://flosight.duckdns.org/`;
@ -330,7 +296,7 @@
var server = mainnet;
else if (cryptocoin == "FLO_TEST")
var server = testnet;
var torrents = [], searchResults = [], trustedIDs = [], torrentSearchIndex;
var trustedIDs = [];
</script>
<script id="userNonPresentationData">
@ -728,6 +694,7 @@
</script>
<script id="default_ui_library">
const domRefs = {};
const { html, render: renderElem, svg } = uhtml;
const uiGlobals = {
connectionErrorNotifications: []
}
@ -899,45 +866,78 @@
}, wait);
};
}
let zIndex = 50
// function required for popups or modals to appear
function openPopup(popupId, pinned) {
zIndex++
getRef(popupId).setAttribute('style', `z-index: ${zIndex}`)
return getRef(popupId).show({ pinned })
}
// hides the popup or modal
function closePopup(options = {}) {
if (popupStack.peek() === undefined)
return;
popupStack.peek().popup.hide(options)
}
document.addEventListener('popupopened', async e => {
document.querySelector('main').inert = true
switch (e.target.id) {
case 'filter_popup':
renderElem(getRef('category_selector'), html`${renderOptions(categories, { groupName: 'category-selector' })}`)
renderElem(getRef('tags_selector'), html`${renderOptions(allTags)}`)
break;
}
})
document.addEventListener('popupclosed', e => {
switch (e.target.id) {
case 'filter_popup':
renderElem(getRef('category_selector'), html``)
renderElem(getRef('tags_selector'), html``)
break;
}
if (popupStack.items.length === 0) {
document.querySelector('main').inert = false
}
zIndex--;
})
</script>
<script>
const render = {
torrentCard(obj) {
const { id, name, description, tags, type = 'misc', size, uploader, startTx, filename, chunks } = obj
const card = getRef('torrent_card_template').content.cloneNode(true).firstElementChild
card.id = id
card.querySelector('.torrent-info').href = `#/torrent/${id}`
card.querySelector('.torrent-card__title').textContent = name
card.querySelector('.torrent-card__uploader').textContent = `by ${uploader}`
card.querySelector('.torrent-card__icon').innerHTML = getIcon(type)
return card
const icon = new DOMParser().parseFromString(getIcon(type), 'image/svg+xml').querySelector('svg')
return html`
<li id=${id} class="torrent-card">
<a class="torrent-info grid" href=${`#/torrent/${id}`}>
<div class="torrent-card__icon torrent-type-icon">${icon}</div>
<h3 class="torrent-card__title">${name}</h3>
<span class="torrent-card__uploader">${`by ${uploader}`}</span>
</a>
<button class="torrent-card__download-button">
Get Torrent
</button>
</li>
`;
},
filterOption(obj) {
const { content, groupName, value } = obj
const option = create('label', {
className: 'filter-option interact'
})
option.innerHTML = `
<input type="${groupName ? 'radio' : 'checkbox'}" name="${groupName ? groupName : ''}" value="${value}">
<div class="option-text">${content}</div>
`
return option
return html`
<label class="filter-option interact">
<input type=${groupName ? 'radio' : 'checkbox'} name=${groupName || ''} value=${value} ?checked=${filteredSearch.category === value || filteredSearch.tags.has(value)}>
<div class="option-text">${content}</div>
</label>
`;
},
selectedFilter(obj) {
const { content, type } = obj
const option = create('div', {
className: 'selected-filter interact'
})
option.dataset.type = type
option.dataset.value = content
option.innerHTML = `
<div class="option-text">${content}</div>
<svg class="icon button__icon--right" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm0-11.414L9.172 7.757 7.757 9.172 10.586 12l-2.829 2.828 1.415 1.415L12 13.414l2.828 2.829 1.415-1.415L13.414 12l2.829-2.828-1.415-1.415L12 10.586z"/></svg>
`
return option
return html`
<button class="selected-filter interact" .dataset=${{ type, value: content }} title="Remove filter">
<div class="option-text">${content}</div>
<svg class="icon icon--right" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm0-11.414L9.172 7.757 7.757 9.172 10.586 12l-2.829 2.828 1.415 1.415L12 13.414l2.828 2.829 1.415-1.415L13.414 12l2.829-2.828-1.415-1.415L12 10.586z"/></svg>
</button>
`;
}
}
function getIcon(type) {
@ -975,11 +975,7 @@
return icon
}
function renderTorrents(torrents) {
const torrentsFrag = document.createDocumentFragment()
torrents.forEach(torrent => {
torrentsFrag.append(render.torrentCard(torrent))
})
return torrentsFrag
return html`${torrents.map(torrent => render.torrentCard(torrent))}`
}
let filteredSearch = {
active: false,
@ -990,8 +986,9 @@
let lastQuery = ''
async function renderSearchResult(searchKey, shouldFilter = false) {
searchKey = decodeURI(searchKey)
if (shouldFilter) {
searchKey = getRef('advance_torrent_search').value.trim() !== '' ? getRef('advance_torrent_search').value.trim() : lastQuery
searchKey = getRef('search_torrent').value.trim() !== '' ? getRef('search_torrent').value.trim() : lastQuery
}
filteredSearch['query'] = searchKey
let result
@ -999,54 +996,44 @@
if (filteredSearch.category && filteredSearch.tags.size) {
result = await getFilteredTorrents(['type'], filteredSearch.category)
result = await getFilteredTorrents(['tags'], [...filteredSearch.tags].join('/'), { torrents: result })
}
else if (filteredSearch.category) {
} else if (filteredSearch.category) {
result = await getFilteredTorrents(['type'], filteredSearch.category)
}
else if (filteredSearch.tags.size) {
} else if (filteredSearch.tags.size) {
result = await getFilteredTorrents(['tags'], [...filteredSearch.tags].join('/'))
}
result = await getFilteredTorrents(['name', 'filename', 'tags'], searchKey, { torrents: result })
}
else {
} else {
result = await getFilteredTorrents(['name', 'filename', 'tags'], searchKey)
}
if (result.length) {
getRef('result_for').innerHTML = `Showing results for <strong>${searchKey}</strong>`
}
else {
getRef('result_for').innerHTML = `No results for <strong>${searchKey}</strong>`
renderElem(getRef('result_for'), html`Showing results for <strong>${searchKey}</strong>`)
} else {
renderElem(getRef('result_for'), html`No results for <strong>${searchKey}</strong>`)
}
getRef('search_result').innerHTML = ``
getRef('search_page_selector').innerHTML = ``
if (result.length > 20) {
const pages = Math.round(result.length / 20)
getRef('search_page_selector').append(createPageButtons(pages))
getRef('search_result').append(renderTorrents(result.slice(0, 20)))
renderElem(getRef('search_page_selector'), createPageButtons(pages))
renderElem(getRef('search_result'), renderTorrents(result.slice(0, 20)))
} else {
renderElem(getRef('search_page_selector'), '')
renderElem(getRef('search_result'), renderTorrents(result))
}
else {
getRef('search_result').append(renderTorrents(result))
}
lastQuery = searchKey
}
const categories = ['movie', 'tv series', 'video', 'music', 'software', 'game', 'image', 'audio', 'misc']
const allTags = ['action', 'adventure', , 'anime', 'comedy', 'crime', 'drama', 'family', 'fantacy', 'horror', 'korean', 'mystery', 'romance', 'sci-fi', 'sport', 'thriller', 'western']
const allTags = ['action', 'adventure', 'anime', 'comedy', 'crime', 'drama', 'family', 'fantasy', 'horror', 'korean', 'mystery', 'romance', 'sci-fi', 'sport', 'thriller', 'western']
function renderOptions(list, options = {}) {
const { groupName } = options
const frag = document.createDocumentFragment()
list.forEach(item => {
return list.map(item => {
const optionObj = {
content: item,
groupName,
groupName: options.groupName,
value: item
}
frag.append(render.filterOption(optionObj))
return render.filterOption(optionObj)
})
return frag
}
const appState = {
@ -1089,16 +1076,9 @@
history.replaceState(null, null, '#/home')
let allTorrents = await getDataFromIDB()
allTorrents = allTorrents.reverse().slice(0, 8)
getRef('torrent_container').innerHTML = ``
getRef('torrent_container').append(renderTorrents(allTorrents))
renderElem(getRef('torrent_container'), renderTorrents(allTorrents))
break;
case 'search':
if (appState.lastPage !== pageId) {
getRef('category_selector').innerHTML = ''
getRef('category_selector').append(renderOptions(categories, { groupName: 'category-selector' }))
getRef('tags_selector').innerHTML = ''
getRef('tags_selector').append(renderOptions(allTags))
}
if (subPageId)
renderSearchResult(subPageId)
break;
@ -1109,7 +1089,7 @@
getRef('torrent_type_icon').innerHTML = getIcon(type)
getRef('torrent_name').textContent = name
getRef('torrent_description').textContent = description
getRef('torrent_tags').textContent = tags.split('/').join('•')
getRef('torrent_tags').innerHTML = tags.replace(/[\/,]/g, ' • ')
getRef('torrent_uploader').innerHTML = `
Uploaded by
<span>FLO address: ${uploader}</span>
@ -1155,13 +1135,6 @@
}
function alphaIncSort(arr, prop) {
return arr.sort((a, b) => a[prop].localeCompare(b[prop]))
}
function alphaDecSort(arr, prop) {
return arr.sort((a, b) => b[prop].localeCompare(a[prop]))
}
async function getFilteredTorrents(keys, searchKey, options = {}) {
let { limit, torrents } = options
if (!torrents) {
@ -1176,65 +1149,38 @@
}
function createPageButtons(pages) {
const paginationFrag = document.createDocumentFragment()
for (let i = 0; i < pages; i++) {
const pageButton = create('sm-chip', {
textContent: (i + 1)
})
pageButton.setAttribute('value', i)
if (i === 0) {
pageButton.setAttribute('selected', '')
}
paginationFrag.append(pageButton)
const pagination = []
for (let index = 0; index < pages; index++) {
pagination.push(html`<sm-chip value="${index}" ?selected=${index === 0}>${index + 1}</sm-chip>`)
}
return paginationFrag
return html`${pagination}`;
}
async function showCategoryTorrents(category) {
const result = await getFilteredTorrents(['type'], category)
getRef('browser_category_torrents').innerHTML = ``
getRef('page_selector').innerHTML = ''
if (result.length) {
getRef('browser_category_torrents').append(renderTorrents(result.reverse().slice(0, 20)))
renderElem(getRef('browser_category_torrents'), renderTorrents(result.reverse().slice(0, 20)))
const pages = Math.round(result.length / 20)
getRef('page_selector').append(createPageButtons(pages))
renderElem(getRef('page_selector'), createPageButtons(pages))
}
else {
getRef('browser_category_torrents').innerHTML = `<h4 class="justify-center text-center">No ${category} torrents 😔</h4>`
renderElem(getRef('browser_category_torrents'), html`<h4 class="justify-center text-center">No ${category} torrents 😔</h4>`)
}
}
async function renderSearchSuggestions(searchKey, advance = false) {
const result = await getFilteredTorrents(['name', 'filename'], searchKey, { limit: 6 })
if (advance) {
getRef('advance_search_suggestions').innerHTML = ``
}
else {
getRef('search_suggestions').innerHTML = ``
}
if (result.length) {
const suggestionsFrag = document.createDocumentFragment()
result.forEach(elem => {
const regEx = new RegExp(`(${searchKey})`, 'gi')
const suggestion = create('a', {
className: 'search-suggestion',
})
const pre = create('pre', {
innerHTML: elem.name.replace(regEx, `<span>$&</span>`),
})
suggestion.append(pre)
suggestion.href = `#/torrent/${elem.id}`
suggestionsFrag.append(suggestion)
})
if (advance) {
getRef('advance_search_suggestions').append(suggestionsFrag)
}
else {
getRef('search_suggestions').append(suggestionsFrag)
}
}
const suggestions = result.map(elem => {
const regEx = new RegExp(`(${searchKey})`, 'gi')
const pre = create('pre', { innerHTML: elem.name.replace(regEx, `<span>$&</span>`), })
return html`
<a class="search-suggestion" href=${`#/torrent/${elem.id}`}>
${pre}
</a>
`;
})
renderElem(getRef('search_suggestions'), html`${suggestions}`)
}
function handleEnter(e) {
@ -1245,6 +1191,7 @@
renderSearchResult(searchKey)
location.hash = `#/search/${filteredSearch.query}`;
e.target.value = ''
renderElem(getRef('search_suggestions'), html``)
break;
case 'ArrowDown':
e.preventDefault()
@ -1274,13 +1221,11 @@
// Event listeners
window.addEventListener('load', e => {
routeTo('loading_page')
getTorrents().then(allTorrents => {
routeTo(window.location.hash)
}).catch(err => {
console.log(err)
})
.catch(err => {
console.log(err)
})
})
window.addEventListener('hashchange', e => {
routeTo(window.location.hash)
@ -1297,44 +1242,36 @@
}
})
getRef('search_torrent').addEventListener('keydown', handleEnter)
getRef('advance_torrent_search').addEventListener('keydown', handleEnter)
getRef('search_suggestions').addEventListener('keydown', handleKeyboardNav)
getRef('advance_search_suggestions').addEventListener('keydown', handleKeyboardNav)
getRef('search_torrent').addEventListener('input', debounce((e) => {
const searchKey = e.target.value.trim()
renderSearchSuggestions(searchKey)
}, 100))
getRef('advance_torrent_search').addEventListener('input', debounce((e) => {
const searchKey = getRef('advance_torrent_search').value.trim()
renderSearchSuggestions(searchKey, true)
}, 100))
getRef('browse_category_selector').addEventListener('change', e => {
showCategoryTorrents(e.detail.value)
showCategoryTorrents(e.target.value)
})
getRef('page_selector').addEventListener('change', async e => {
const category = getRef('browse_category_selector').value
const result = await getFilteredTorrents(['type'], category)
const startIndex = parseInt(e.detail.value) * 20
const endIndex = ((parseInt(e.detail.value) * 20) + 30) < result.length ? (parseInt(e.detail.value) * 20) + 20 : result.length
const startIndex = parseInt(e.target.value) * 20
const endIndex = (parseInt(e.target.value) * 20) + 30
setTimeout(() => {
getRef('browser_category_torrents').scrollIntoView({ behavior: 'smooth', block: 'start' })
getRef('browser_category_torrents').innerHTML = ``
getRef('browser_category_torrents').append(renderTorrents(result.reverse().slice(startIndex, endIndex)))
renderElem(getRef('browser_category_torrents'), renderTorrents(result.reverse().slice(startIndex, endIndex)))
}, 200);
})
getRef('search_page_selector').addEventListener('change', async e => {
const result = await getFilteredTorrents(['name', 'filename', 'tags'], lastQuery)
const startIndex = parseInt(e.detail.value) * 20
const endIndex = ((parseInt(e.detail.value) * 20) + 30) < result.length ? (parseInt(e.detail.value) * 20) + 20 : result.length
const startIndex = parseInt(e.target.value) * 20
const endIndex = (parseInt(e.target.value) * 20) + 30
setTimeout(() => {
getRef('search_result').scrollIntoView({ behavior: 'smooth', block: 'start' })
getRef('search_result').innerHTML = ``
getRef('search_result').append(renderTorrents(result.slice(startIndex, endIndex)))
}, 200);
renderElem(getRef('search_result'), renderTorrents(result.slice(startIndex, endIndex)))
}, 100);
})
function addFilter() {
const selectedCategory = getRef('category_selector').querySelector('input:checked')
@ -1345,60 +1282,46 @@
getRef('tags_selector').querySelectorAll('input:checked').forEach(tag => {
filteredSearch['tags'].add(tag.value)
})
if (filteredSearch.category || filteredSearch.tags.size) {
filteredSearch.isActive = true
getRef('filters_bar').classList.remove('hidden')
}
else {
getRef('filters_bar').classList.add('hidden')
}
renderSelectedFilters()
renderSearchResult('', true);
getRef('filter_popup').hide()
closePopup()
}
function resetFilter() {
filteredSearch.isActive = false
filteredSearch.category = undefined
getRef('filter_form').reset()
const selectedCategory = getRef('category_selector').querySelector('input:checked')
if (selectedCategory) {
selectedCategory.checked = false
}
filteredSearch['tags'].clear()
getRef('tags_selector').querySelectorAll('input:checked').forEach(tag => {
tag.checked = false
})
getRef('selected_filters_container').innerHTML = ''
getRef('filters_bar').classList.add('hidden')
renderSelectedFilters()
renderSearchResult('', true);
closePopup()
}
function renderSelectedFilters() {
getRef('selected_filters_container').innerHTML = ''
const frag = document.createDocumentFragment()
const appliedFilters = []
if (filteredSearch.category) {
frag.append(render.selectedFilter({ content: filteredSearch.category, type: 'category' }))
appliedFilters.push(render.selectedFilter({ content: filteredSearch.category, type: 'category' }))
}
filteredSearch.tags.forEach(tag => {
frag.append(render.selectedFilter({ content: tag, type: 'tag' }))
appliedFilters.push(render.selectedFilter({ content: tag, type: 'tag' }))
})
getRef('selected_filters_container').append(frag)
renderElem(getRef('selected_filters_container'), html`${appliedFilters}`)
if (!filteredSearch.category && !filteredSearch.tags.size) {
filteredSearch.isActive = false
getRef('filters_bar').classList.add('hidden')
delete getRef('filter_button').dataset.active
} else {
filteredSearch.isActive = true
getRef('filters_bar').classList.remove('hidden')
getRef('filter_button').dataset.active = appliedFilters.length
}
}
getRef('selected_filters_container').addEventListener('click', e => {
if (e.target.closest('.selected-filter')) {
const target = e.target.closest('.selected-filter')
if (target.dataset.type === 'category') {
filteredSearch.category = undefined
getRef('category_selector').querySelector('input:checked').checked = false
}
else {
} else {
filteredSearch.tags.delete(target.dataset.value)
getRef('tags_selector').querySelector(`input[value="${target.dataset.value}"]`).checked = false
}
target.remove()
if (!filteredSearch.category && !filteredSearch.tags.size) {
filteredSearch.isActive = false
getRef('filters_bar').classList.add('hidden')
}
renderSelectedFilters()
}
renderSearchResult('', true);
})