WIP torrent search list

This commit is contained in:
2022-03-07 00:05:20 +01:00
parent baf16f2a55
commit 8a75fd7c22

View File

@@ -1,90 +1,123 @@
<template> <template>
<div v-if="show" class="container"> <div v-if="show" class="container">
<h2 class="torrentHeader-text">Searching for: {{ editedSearchQuery || query }}</h2> <h2 class="torrentHeader-text editable">
<!-- <div class="torrentHeader"> Searching for:
<span class="torrentHeader-text">Searching for:&nbsp;</span> <span :contenteditable="!edit" @input="this.handleInput">{{
query
}}</span>
<IconSearch
class="icon"
v-if="editedSearchQuery && editedSearchQuery.length"
/>
<IconEdit v-else class="icon" @click="() => (this.edit = !this.edit)" />
</h2>
<span id="search" :contenteditable="editSearchQuery ? true : false" class="torrentHeader-text editable">{{ editedSearchQuery || query }}</span> <div v-if="!loading">
<svg v-if="!editSearchQuery" class="torrentHeader-editIcon" @click="toggleEditSearchQuery">
<use xlink:href="#icon_radar"></use>
</svg>
<svg v-else class="torrentHeader-editIcon" @click="toggleEditSearchQuery">
<use xlink:href="#icon_check"></use>
</svg>
</div> -->
<div v-if="listLoaded">
<div v-if="torrents.length > 0"> <div v-if="torrents.length > 0">
<!-- <ul class="filter">
<li class="filter-item" v-for="(item, index) in release_types" @click="applyFilter(item, index)" :class="{'active': item === selectedRelaseType}">{{ item }}</li>
</ul> -->
<toggle-button :options="release_types" :selected.sync="selectedRelaseType" class="toggle"></toggle-button>
<table> <table>
<tr class="table__header noselect"> <thead class="table__header noselect">
<th @click="sortTable('name')" :class="selectedSortableClass('name')"> <tr>
<th
v-for="column in columns"
:key="column"
@click="sortTable(column)"
:class="column === this.selectedColumn ? 'active' : null"
>
{{ column }}
<span v-if="prevCol === column && direction"></span>
<span v-if="prevCol === column && !direction"></span>
</th>
</tr>
<!-- <th
@click="sortTable('name')"
:class="selectedSortableClass('name')"
>
<span>Name</span> <span>Name</span>
<span v-if="prevCol === 'name' && direction"></span> <span v-if="prevCol === 'name' && direction"></span>
<span v-if="prevCol === 'name' && !direction"></span> <span v-if="prevCol === 'name' && !direction"></span>
</th> </th>
<th @click="sortTable('seed')" :class="selectedSortableClass('seed')"> <th
@click="sortTable('seed')"
:class="selectedSortableClass('seed')"
>
<span>Seed</span> <span>Seed</span>
<span v-if="prevCol === 'seed' && direction"></span> <span v-if="prevCol === 'seed' && direction"></span>
<span v-if="prevCol === 'seed' && !direction"></span> <span v-if="prevCol === 'seed' && !direction"></span>
</th> </th>
<th @click="sortTable('size')" :class="selectedSortableClass('size')"> <th
@click="sortTable('size')"
:class="selectedSortableClass('size')"
>
<span>Size</span> <span>Size</span>
<span v-if="prevCol === 'size' && direction"></span> <span v-if="prevCol === 'size' && direction"></span>
<span v-if="prevCol === 'size' && !direction"></span> <span v-if="prevCol === 'size' && !direction"></span>
</th>
<th> <th>
<span>Magnet</span> <span>Magnet</span>
</th> </th> -->
</tr> </thead>
<tr v-for="torrent in torrents" class="table__content">
<tbody>
<tr
v-for="torrent in torrents"
class="table__content"
:key="torrent.magnet"
>
<td @click="expand($event, torrent.name)">{{ torrent.name }}</td> <td @click="expand($event, torrent.name)">{{ torrent.name }}</td>
<td @click="expand($event, torrent.name)">{{ torrent.seed }}</td> <td @click="expand($event, torrent.name)">{{ torrent.seed }}</td>
<td @click="expand($event, torrent.name)">{{ torrent.size }}</td> <td @click="expand($event, torrent.name)">{{ torrent.size }}</td>
<td @click="sendTorrent(torrent.magnet, torrent.name, $event)" class="download"> <td
<svg class="download__icon"><use xlink:href="#iconUnmatched"></use></svg> @click="sendTorrent(torrent.magnet, torrent.name, $event)"
class="download"
>
<IconMagnet />
</td> </td>
</tr> </tr>
</tbody>
</table> </table>
<div style=" <div style="display: flex; justify-content: center; padding: 1rem">
display: flex; <seasonedButton @click="resetTorrentsAndToggleEditSearchQuery"
justify-content: center; >Edit search query</seasonedButton
padding: 1rem; >
">
<seasonedButton @click="resetTorrentsAndToggleEditSearchQuery">Edit search query</seasonedButton>
</div> </div>
</div> </div>
<div v-else style="display: flex; <div
v-else
style="
display: flex;
padding-bottom: 2rem; padding-bottom: 2rem;
justify-content: center; justify-content: center;
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
align-items: center;"> align-items: center;
"
>
<h2>No results found</h2> <h2>No results found</h2>
<br /> <br />
<div class="editQuery" v-if="editSearchQuery"> <div class="editQuery" v-if="editSearchQuery">
<seasonedInput
placeholder="Torrent query"
:value.sync="editedSearchQuery"
@enter="fetchTorrents(editedSearchQuery)"
/>
<seasonedInput placeholder="Torrent query" icon="_torrents" :value.sync="editedSearchQuery" @enter="fetchTorrents(editedSearchQuery)" /> <div style="height: 45px; width: 5px"></div>
<div style="height: 45px; width: 5px;"></div> <seasonedButton @click="fetchTorrents(editedSearchQuery)"
>Search</seasonedButton
<seasonedButton @click="fetchTorrents(editedSearchQuery)">Search</seasonedButton> >
</div> </div>
<seasonedButton @click="toggleEditSearchQuery" :active="editSearchQuery ? true : false">Edit search query</seasonedButton> <seasonedButton
@click="toggleEditSearchQuery"
:active="editSearchQuery ? true : false"
>Edit search query</seasonedButton
>
</div> </div>
</div> </div>
<div v-else class="torrentloader"><i></i></div> <div v-else class="torrentloader"><i></i></div>
@@ -92,17 +125,28 @@
</template> </template>
<script> <script>
import storage from '@/storage' import storage from "@/storage";
import store from '@/store' import store from "@/store";
import { sortableSize } from '@/utils' import { sortableSize } from "@/utils";
import { searchTorrents, addMagnet } from '@/api' import { searchTorrents, addMagnet } from "@/api";
import SeasonedButton from '@/components/ui/SeasonedButton' import IconMagnet from "../icons/IconMagnet";
import SeasonedInput from '@/components/ui/SeasonedInput' import IconEdit from "../icons/IconEdit";
import ToggleButton from '@/components/ui/ToggleButton' import IconSearch from "../icons/IconSearch";
import SeasonedButton from "@/components/ui/SeasonedButton";
import SeasonedInput from "@/components/ui/SeasonedInput";
import ToggleButton from "@/components/ui/ToggleButton";
export default { export default {
components: { SeasonedButton, SeasonedInput, ToggleButton }, components: {
IconMagnet,
IconEdit,
IconSearch,
SeasonedButton,
SeasonedInput,
ToggleButton
},
props: { props: {
query: { query: {
type: String, type: String,
@@ -118,171 +162,204 @@ export default {
}, },
data() { data() {
return { return {
listLoaded: false, edit: true,
loading: false,
torrents: [], torrents: [],
torrentResponse: undefined, torrentResponse: undefined,
currentPage: 0, currentPage: 0,
prevCol: '', prevCol: "",
direction: false, direction: false,
release_types: ['all'], release_types: ["all"],
selectedRelaseType: 'all', selectedRelaseType: "all",
editSearchQuery: false, editSearchQuery: false,
editedSearchQuery: '' editedSearchQuery: "",
}
columns: ["name", "seed", "size", "magnet"],
selectedColumn: null
};
}, },
beforeMount() { created() {
if (localStorage.getItem('admin')) { this.fetchTorrents().then(_ => this.sortTable("size"));
this.fetchTorrents()
}
store.dispatch('torrentModule/reset')
}, },
watch: { watch: {
selectedRelaseType: function(newValue) { selectedRelaseType: function (newValue) {
this.applyFilter(newValue) this.applyFilter(newValue);
} }
}, },
methods: { methods: {
selectedSortableClass(headerName) { selectedSortableClass(headerName) {
return headerName === this.prevCol ? 'active' : '' return headerName === this.prevCol ? "active" : "";
}, },
resetTorrentsAndToggleEditSearchQuery() { resetTorrentsAndToggleEditSearchQuery() {
this.torrents = [] this.torrents = [];
this.toggleEditSearchQuery() this.toggleEditSearchQuery();
}, },
toggleEditSearchQuery() { toggleEditSearchQuery() {
this.editSearchQuery = !this.editSearchQuery; this.editSearchQuery = !this.editSearchQuery;
}, },
expand(event, name) { expand(event, name) {
const existingExpandedElement = document.getElementsByClassName('expanded')[0] const existingExpandedElement =
document.getElementsByClassName("expanded")[0];
const clickedElement = event.target.parentNode; const clickedElement = event.target.parentNode;
const scopedStyleDataVariable = Object.keys(clickedElement.dataset)[0] const scopedStyleDataVariable = Object.keys(clickedElement.dataset)[0];
if (existingExpandedElement) { if (existingExpandedElement) {
const expandedSibling = event.target.parentNode.nextSibling.className === 'expanded' const expandedSibling =
event.target.parentNode.nextSibling.className === "expanded";
existingExpandedElement.remove() existingExpandedElement.remove();
const table = document.getElementsByTagName('table')[0] const table = document.getElementsByTagName("table")[0];
table.style.display = 'block' table.style.display = "block";
if (expandedSibling) { if (expandedSibling) {
return return;
} }
} }
const nameRow = document.createElement('tr') const nameRow = document.createElement("tr");
const nameCol = document.createElement('td') const nameCol = document.createElement("td");
nameRow.className = 'expanded' nameRow.className = "expanded";
nameRow.dataset[scopedStyleDataVariable] = ""; nameRow.dataset[scopedStyleDataVariable] = "";
nameCol.innerText = name nameCol.innerText = name;
nameCol.dataset[scopedStyleDataVariable] = ""; nameCol.dataset[scopedStyleDataVariable] = "";
nameRow.appendChild(nameCol) nameRow.appendChild(nameCol);
clickedElement.insertAdjacentElement('afterend', nameRow) clickedElement.insertAdjacentElement("afterend", nameRow);
}, },
sendTorrent(magnet, name, event){ sendTorrent(magnet, name, event) {
this.$notifications.info({ this.$notifications.info({
title: 'Adding torrent 🦜', title: "Adding torrent 🦜",
description: this.query, description: this.query,
timeout: 3000 timeout: 3000
}) });
event.target.parentNode.classList.add('active') event.target.parentNode.classList.add("active");
addMagnet(magnet, name, this.tmdb_id) addMagnet(magnet, name, this.tmdb_id)
.catch((resp) => { console.log('error:', resp.data) }) .catch(resp => {
.then((resp) => { console.log("error:", resp.data);
console.log('addTorrent resp: ', resp) })
.then(resp => {
console.log("addTorrent resp: ", resp);
this.$notifications.success({ this.$notifications.success({
title: 'Torrent added 🎉', title: "Torrent added 🎉",
description: this.query, description: this.query,
timeout: 3000 timeout: 3000
}) });
}) });
}, },
sortTable(col, sameDirection=false) { sortTable(col, sameDirection = false) {
if (this.prevCol === col && sameDirection === false) { if (this.prevCol === col && sameDirection === false) {
this.direction = !this.direction this.direction = !this.direction;
} }
switch (col) { if (col === "name") this.sortName();
case 'name': else if (col === "seed") this.sortSeed();
this.sortName() else if (col === "size") this.sortSize();
break
case 'seed': this.prevCol = col;
this.sortSeed()
break
case 'size':
this.sortSize()
break
}
this.prevCol = col
}, },
sortName() { sortName() {
const torrentsCopy = [...this.torrents] const torrentsCopy = [...this.torrents];
if (this.direction) { if (this.direction) {
this.torrents = torrentsCopy.sort((a, b) => (a.name < b.name) ? 1 : -1) this.torrents = torrentsCopy.sort((a, b) => (a.name < b.name ? 1 : -1));
} else { } else {
this.torrents = torrentsCopy.sort((a, b) => (a.name > b.name) ? 1 : -1) this.torrents = torrentsCopy.sort((a, b) => (a.name > b.name ? 1 : -1));
} }
}, },
sortSeed() { sortSeed() {
const torrentsCopy = [...this.torrents] const torrentsCopy = [...this.torrents];
if (this.direction) { if (this.direction) {
this.torrents = torrentsCopy.sort((a, b) => parseInt(a.seed) - parseInt(b.seed)); this.torrents = torrentsCopy.sort(
(a, b) => parseInt(a.seed) - parseInt(b.seed)
);
} else { } else {
this.torrents = torrentsCopy.sort((a, b) => parseInt(b.seed) - parseInt(a.seed)); this.torrents = torrentsCopy.sort(
(a, b) => parseInt(b.seed) - parseInt(a.seed)
);
} }
}, },
sortSize() { sortSize() {
const torrentsCopy = [...this.torrents] const torrentsCopy = [...this.torrents];
if (this.direction) { if (this.direction) {
this.torrents = torrentsCopy.sort((a, b) => parseInt(sortableSize(a.size)) - parseInt(sortableSize(b.size))); this.torrents = torrentsCopy.sort(
(a, b) =>
parseInt(sortableSize(a.size)) - parseInt(sortableSize(b.size))
);
} else { } else {
this.torrents = torrentsCopy.sort((a, b) => parseInt(sortableSize(b.size)) - parseInt(sortableSize(a.size))); this.torrents = torrentsCopy.sort(
(a, b) =>
parseInt(sortableSize(b.size)) - parseInt(sortableSize(a.size))
);
} }
}, },
findRelaseTypes() { findRelaseTypes() {
this.torrents.forEach(item => this.release_types.push(...item.release_type)) this.torrents.forEach(item =>
this.release_types = [...new Set(this.release_types)] this.release_types.push(...item.release_type)
);
this.release_types = [...new Set(this.release_types)];
}, },
applyFilter(item, index) { applyFilter(item, index) {
this.selectedRelaseType = item; this.selectedRelaseType = item;
const torrents = [...this.torrentResponse] const torrents = [...this.torrentResponse];
if (item === 'all') { if (item === "all") {
this.torrents = torrents this.torrents = torrents;
this.sortTable(this.prevCol, true) this.sortTable(this.prevCol, true);
return return;
} }
this.torrents = torrents.filter(torrent => torrent.release_type.includes(item)) this.torrents = torrents.filter(torrent =>
this.sortTable(this.prevCol, true) torrent.release_type.includes(item)
);
this.sortTable(this.prevCol, true);
}, },
updateResultCountInStore() { updateResultCountInStore() {
store.dispatch('torrentModule/setResults', this.torrents) store.dispatch("torrentModule/setResults", this.torrents);
store.dispatch('torrentModule/setResultCount', this.torrentResponse.length) store.dispatch(
"torrentModule/setResultCount",
this.torrentResponse.length
);
}, },
fetchTorrents(query=undefined){ filterDeadTorrents(torrents) {
this.listLoaded = false; return torrents.filter(torrent => {
if (isNaN(torrent.seed)) return false;
return parseInt(torrent.seed) > 0;
});
},
fetchTorrents(query = undefined) {
this.loading = true;
this.editSearchQuery = false; this.editSearchQuery = false;
searchTorrents(query || this.query, 'all', this.currentPage, storage.token) return searchTorrents(query || this.query)
.then(data => { .then(data => {
this.torrentResponse = [...data.results]; const { results } = data;
this.torrents = data.results; if (results) {
this.listLoaded = true; this.torrentResponse = results;
this.torrents = this.filterDeadTorrents(results);
} else {
this.torrents = [];
}
}) })
.then(this.updateResultCountInStore) .then(this.updateResultCountInStore)
.then(this.findRelaseTypes) .then(this.findRelaseTypes)
.catch(e => { .catch(e => {
const error = e.toString() console.log("e:", e);
this.errorMessage = error.indexOf('401') != -1 ? 'Permission denied' : 'Nothing found'; const error = e.toString();
this.listLoaded = true; this.errorMessage =
error.indexOf("401") != -1 ? "Permission denied" : "Nothing found";
})
.finally(() => {
this.loading = false;
}); });
}, },
handleInput(event) {
this.editedSearchQuery = event.target.innerText;
console.log("edit text:", this.editedSearchQuery);
} }
} }
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -301,11 +378,153 @@ export default {
width: 100%; width: 100%;
} }
} }
$checkboxSize: 20px;
$ui-border-width: 2px;
.checkbox {
display: flex;
flex-direction: row;
margin-bottom: $checkboxSize * 0.5;
input[type="checkbox"] {
display: block;
opacity: 0;
position: absolute;
+ div {
position: relative;
display: inline-block;
padding-left: 1.25rem;
font-size: 20px;
line-height: $checkboxSize + $ui-border-width * 2;
left: $checkboxSize;
cursor: pointer;
&::before {
content: "";
display: inline-block;
position: absolute;
left: -$checkboxSize;
border: $ui-border-width solid var(--color-green);
width: $checkboxSize;
height: $checkboxSize;
}
&::after {
transition: all 0.3s ease;
content: "";
position: absolute;
display: inline-block;
left: -$checkboxSize + $ui-border-width;
top: $ui-border-width;
width: $checkboxSize + $ui-border-width;
height: $checkboxSize + $ui-border-width;
}
}
&:checked {
+ div::after {
background-color: var(--color-green);
opacity: 1;
}
}
&:hover:not(checked) {
+ div::after {
background-color: var(--color-green);
opacity: 0.4;
}
}
&:focus {
+ div::before {
outline: 2px solid Highlight;
outline-style: auto;
outline-color: -webkit-focus-ring-color;
}
}
}
}
</style> </style>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "./src/scss/variables"; @import "src/scss/variables";
@import "./src/scss/media-queries"; @import "src/scss/media-queries";
@import "./src/scss/elements"; @import "src/scss/elements";
h2 {
font-size: 20px;
}
thead {
user-select: none;
-webkit-user-select: none;
color: var(--background-color);
text-transform: uppercase;
cursor: pointer;
background-color: var(--text-color);
letter-spacing: 0.8px;
font-size: 1rem;
border: 1px solid var(--text-color-90);
th:first-of-type {
border-top-left-radius: 8px;
}
th:last-of-type {
border-top-right-radius: 8px;
}
}
tbody {
tr > td:first-of-type {
white-space: unset;
}
tr > td:not(td:first-of-type) {
text-align: center;
}
tr > td:last-of-type {
cursor: pointer;
}
tr td:first-of-type {
border-left: 1px solid var(--text-color-90);
}
tr td:last-of-type {
border-right: 1px solid var(--text-color-90);
}
tr:last-of-type {
td {
border-bottom: 1px solid var(--text-color-90);
}
td:first-of-type {
border-bottom-left-radius: 8px;
}
td:last-of-type {
border-bottom-right-radius: 8px;
}
}
tr:nth-child(even) {
background-color: var(--background-70);
}
}
th,
td {
padding: 0.35rem 0.25rem;
white-space: nowrap;
svg {
width: 24px;
fill: var(--text-color);
}
}
.toggle { .toggle {
max-width: unset !important; max-width: unset !important;
@@ -323,17 +542,21 @@ export default {
justify-content: center; justify-content: center;
padding-bottom: 20px; padding-bottom: 20px;
&-text { &-text {
font-weight: 400; font-weight: 400;
text-transform: uppercase; text-transform: uppercase;
font-size: 14px; font-size: 20px;
color: $green; // color: $green;
text-align: center; text-align: center;
margin: 0; margin: 0;
@include tablet-min { .icon {
font-size: 16px vertical-align: text-top;
margin-left: 1rem;
fill: var(--text-color);
width: 22px;
height: 22px;
// stroke: white !important;
} }
&.editable { &.editable {
@@ -355,59 +578,67 @@ export default {
} }
table { table {
border-collapse: collapse; // border-collapse: collapse;
border-spacing: 0;
margin-top: 1rem;
width: 100%; width: 100%;
table-layout: fixed; // table-layout: fixed;
} }
.table__content, .table__header { // .table__content,
display: flex; // .table__header {
padding: 0; // display: flex;
border-left: 1px solid $text-color; // padding: 0;
border-right: 1px solid $text-color; // border-left: 1px solid $text-color;
border-bottom: 1px solid $text-color; // border-right: 1px solid $text-color;
// border-bottom: 1px solid $text-color;
th, td { // th,
display: flex; // td {
flex-direction: column; // display: flex;
flex-basis: 100%; // flex-direction: column;
// flex-basis: 100%;
padding: 0.4rem; // padding: 0.4rem;
white-space: nowrap; // white-space: nowrap;
text-overflow: ellipsis; // text-overflow: ellipsis;
overflow: hidden; // overflow: hidden;
min-width: 75px; // min-width: 75px;
} // }
th:first-child, td:first-child { // th:first-child,
flex: 1; // td:first-child {
} // flex: 1;
// }
th:not(:first-child), td:not(:first-child) { // th:not(:first-child),
flex: 0.2; // td:not(:first-child) {
} // flex: 0.2;
// }
th:nth-child(2), td:nth-child(2) { // th:nth-child(2),
flex: 0.1; // td:nth-child(2) {
} // flex: 0.1;
// }
@include mobile-only { // @include mobile-only {
th:first-child, td:first-child { // th:first-child,
display: none; // td:first-child {
// display: none;
&.show { // &.show {
display: block; // display: block;
align: flex-end; // align: flex-end;
} // }
} // }
th:not(:first-child), td:not(:first-child) { // th:not(:first-child),
flex: 1; // td:not(:first-child) {
} // flex: 1;
} // }
// }
} // }
.table__content { .table__content {
td:not(:last-child) { td:not(:last-child) {
@@ -422,58 +653,54 @@ table {
border-bottom-right-radius: 3px; border-bottom-right-radius: 3px;
} }
.table__header { // .table__header {
color: $text-color; // color: $text-color;
text-transform: uppercase; // text-transform: uppercase;
cursor: pointer; // cursor: pointer;
background-color: $background-color-secondary; // background-color: $background-color-secondary;
border-top: 1px solid $text-color; // border-top: 1px solid $text-color;
border-top-left-radius: 3px; // border-top-left-radius: 3px;
border-top-right-radius: 3px; // border-top-right-radius: 3px;
th { // th {
display: flex; // display: flex;
flex-direction: row; // flex-direction: row;
font-weight: 400; // font-weight: 400;
letter-spacing: 0.7px; // letter-spacing: 0.7px;
// font-size: 1.08rem; // // font-size: 1.08rem;
font-size: 15px; // font-size: 15px;
&::before { // &::before {
content: ''; // content: "";
min-width: 0.2rem; // min-width: 0.2rem;
} // }
span:first-child {
margin-right: 0.6rem;
}
span:nth-child(2) {
margin-right: 0.1rem;
}
}
th:not(:last-child) {
border-right: 1px solid $text-color;
}
}
// span:first-child {
// margin-right: 0.6rem;
// }
// span:nth-child(2) {
// margin-right: 0.1rem;
// }
// }
// th:not(:last-child) {
// border-right: 1px solid $text-color;
// }
// }
.editQuery { .editQuery {
display: flex; display: flex;
width: 70%; width: 70%;
justify-content: center; justify-content: center;
margin-bottom: 1rem;
@include mobile-only { @include mobile-only {
width: 90%; width: 90%;
} }
} }
.download { .download {
&__icon { &__icon {
fill: $text-color-70; fill: $text-color-70;
height: 1.2rem; height: 1.2rem;
@@ -506,7 +733,7 @@ table {
&:after { &:after {
border: 5px solid $green; border: 5px solid $green;
border-radius: 50%; border-radius: 50%;
content: ''; content: "";
left: 10px; left: 10px;
position: absolute; position: absolute;
top: 16px; top: 16px;
@@ -514,6 +741,8 @@ table {
} }
} }
@keyframes load { @keyframes load {
100% { transform: rotate(360deg); } 100% {
transform: rotate(360deg);
}
} }
</style> </style>