Upgraded all components to vue 3 & typescript

This commit is contained in:
2022-08-06 16:10:13 +02:00
parent 890d0c428d
commit d12dfc3c8e
34 changed files with 3508 additions and 3554 deletions

View File

@@ -0,0 +1,6 @@
<template>
<code
>Monitor active torrents requested. Requires authentication with owners plex
library!</code
>
</template>

View File

@@ -0,0 +1,157 @@
<template>
<div class="container" v-if="query?.length">
<h2 class="torrent-header-text">
Searching for: <span class="query">{{ query }}</span>
</h2>
<loader v-if="loading" type="section" />
<div v-else>
<div v-if="torrents.length > 0" class="torrent-table">
<torrent-table :torrents="torrents" @magnet="addTorrent" />
<slot />
</div>
<div v-else class="no-results">
<h2>No results found</h2>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, inject, defineProps } from "vue";
import { useStore } from "vuex";
import { sortableSize } from "../../utils";
import { searchTorrents, addMagnet } from "../../api";
import Loader from "@/components/ui/Loader.vue";
import TorrentTable from "@/components/torrent/TorrentTable.vue";
import type { Ref } from "vue";
import type ITorrent from "../../interfaces/ITorrent";
interface Props {
query: string;
tmdb_id?: number;
}
const loading: Ref<boolean> = ref(true);
const torrents: Ref<ITorrent[]> = ref([]);
const release_types: Ref<string[]> = ref(["all"]);
const props = defineProps<Props>();
const store = useStore();
const notifications: {
info;
success;
error;
} = inject("notifications");
fetchTorrents();
function fetchTorrents() {
loading.value = true;
searchTorrents(props.query)
.then(torrentResponse => (torrents.value = torrentResponse?.results))
.then(() => updateResultCountDisplay())
.finally(() => (loading.value = false));
}
function updateResultCountDisplay() {
store.dispatch("torrentModule/setResults", torrents.value);
store.dispatch(
"torrentModule/setResultCount",
torrents.value?.length || -1
);
}
function addTorrent(torrent: ITorrent) {
const { name, magnet } = torrent;
notifications.info({
title: "Adding torrent 🧲",
description: props.query,
timeout: 3000
});
addMagnet(magnet, name, props.tmdb_id)
.then(resp => {
notifications.success({
title: "Torrent added 🎉",
description: props.query,
timeout: 3000
});
})
.catch(resp => {
console.log("Error while adding torrent:", resp?.data);
notifications.error({
title: "Failed to add torrent 🙅‍♀️",
description: "Check console for more info",
timeout: 3000
});
});
}
</script>
<style lang="scss" scoped>
@import "src/scss/variables";
@import "src/scss/media-queries";
@import "src/scss/elements";
h2 {
font-size: 20px;
}
.toggle {
max-width: unset !important;
margin: 1rem 0;
}
.container {
background-color: $background-color;
}
.no-results {
display: flex;
padding-bottom: 2rem;
justify-content: center;
flex-direction: column;
width: 100%;
align-items: center;
}
.torrent-header-text {
font-weight: 300;
text-transform: uppercase;
font-size: 20px;
color: var(--text-color);
text-align: center;
margin: 0;
.query {
font-weight: 500;
white-space: pre;
}
@include mobile {
text-align: left;
}
}
.download {
&__icon {
fill: $text-color-70;
height: 1.2rem;
&:hover {
fill: $text-color;
cursor: pointer;
}
}
&.active &__icon {
fill: $green;
}
}
</style>

View File

@@ -0,0 +1,250 @@
<template>
<table>
<thead class="table__header noselect">
<tr>
<th
v-for="column in columns"
:key="column"
@click="sortTable(column)"
:class="column === selectedColumn ? 'active' : null"
>
{{ column }}
<span v-if="prevCol === column && direction"></span>
<span v-if="prevCol === column && !direction"></span>
</th>
</tr>
</thead>
<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.seed }}</td>
<td @click="expand($event, torrent.name)">{{ torrent.size }}</td>
<td @click="() => emit('magnet', torrent)" class="download">
<IconMagnet />
</td>
</tr>
</tbody>
</table>
</template>
<script setup lang="ts">
import { ref, defineProps, defineEmits } from "vue";
import IconMagnet from "@/icons/IconMagnet.vue";
import { sortableSize } from "../../utils";
import type { Ref } from "vue";
import type ITorrent from "../../interfaces/ITorrent";
interface Props {
torrents: Array<ITorrent>;
}
interface Emit {
(e: "magnet", torrent: ITorrent): void;
}
const props = defineProps<Props>();
const emit = defineEmits<Emit>();
const columns: string[] = ["name", "seed", "size", "add"];
const torrents: Ref<ITorrent[]> = ref(props.torrents);
const direction: Ref<boolean> = ref(false);
const selectedColumn: Ref<string> = ref(columns[0]);
const prevCol: Ref<string> = ref("");
function expand(event: MouseEvent, text: string) {
const elementClicked = event.target as HTMLElement;
const tableRow = elementClicked.parentElement;
const scopedStyleDataVariable = Object.keys(tableRow.dataset)[0];
const existingExpandedElement =
document.getElementsByClassName("expanded")[0];
const clickedSameTwice =
existingExpandedElement?.previousSibling?.isEqualNode(tableRow);
if (existingExpandedElement) {
existingExpandedElement.remove();
// Clicked the same element twice, remove and return
// not recreate and collapse
if (clickedSameTwice) return;
}
const expandedRow = document.createElement("tr");
const expandedCol = document.createElement("td");
expandedRow.dataset[scopedStyleDataVariable] = "";
expandedCol.dataset[scopedStyleDataVariable] = "";
expandedRow.className = "expanded";
expandedCol.innerText = text;
expandedCol.colSpan = 4;
expandedRow.appendChild(expandedCol);
tableRow.insertAdjacentElement("afterend", expandedRow);
}
function sortTable(col, sameDirection = false) {
if (prevCol.value === col && sameDirection === false) {
direction.value = !direction.value;
}
if (col === "name") sortName();
else if (col === "seed") sortSeed();
else if (col === "size") sortSize();
prevCol.value = col;
}
function sortName() {
const torrentsCopy = [...torrents.value];
if (direction.value) {
torrents.value = torrentsCopy.sort((a, b) => (a.name < b.name ? 1 : -1));
} else {
torrents.value = torrentsCopy.sort((a, b) => (a.name > b.name ? 1 : -1));
}
}
function sortSeed() {
const torrentsCopy = [...torrents.value];
if (direction.value) {
torrents.value = torrentsCopy.sort(
(a, b) => parseInt(a.seed) - parseInt(b.seed)
);
} else {
torrents.value = torrentsCopy.sort(
(a, b) => parseInt(b.seed) - parseInt(a.seed)
);
}
}
function sortSize() {
const torrentsCopy = [...torrents.value];
if (direction.value) {
torrents.value = torrentsCopy.sort(
(a, b) => sortableSize(a.size) - sortableSize(b.size)
);
} else {
torrents.value = torrentsCopy.sort(
(a, b) => sortableSize(b.size) - sortableSize(a.size)
);
}
}
</script>
<style lang="scss" scoped>
@import "src/scss/variables";
@import "src/scss/media-queries";
@import "src/scss/elements";
table {
border-spacing: 0;
margin-top: 0.5rem;
width: 100%;
// border-collapse: collapse;
border-radius: 0.5rem;
overflow: hidden;
}
th,
td {
border: 0.5px solid var(--background-color-40);
@include mobile {
white-space: nowrap;
padding: 0;
}
}
thead {
position: relative;
user-select: none;
-webkit-user-select: none;
color: var(--table-header-text-color);
text-transform: uppercase;
cursor: pointer;
background-color: var(--table-background-color);
// background-color: black;
// color: var(--color-green);
letter-spacing: 0.8px;
font-size: 1rem;
th:last-of-type {
padding-right: 0.4rem;
}
}
tbody {
// first column
tr td:first-of-type {
position: relative;
padding: 0 0.3rem;
cursor: default;
word-break: break-all;
border-left: 1px solid var(--table-background-color);
@include mobile {
max-width: 40vw;
overflow-x: hidden;
}
}
// all columns except first
tr td:not(td:first-of-type) {
text-align: center;
white-space: nowrap;
}
// last column
tr td:last-of-type {
vertical-align: middle;
cursor: pointer;
border-right: 1px solid var(--table-background-color);
svg {
width: 21px;
display: block;
margin: auto;
padding: 0.3rem 0;
fill: var(--text-color);
}
}
// alternate background color per row
tr:nth-child(even) {
background-color: var(--background-70);
}
// last element rounded corner border
tr:last-of-type {
td {
border-bottom: 1px solid var(--table-background-color);
}
td:first-of-type {
border-bottom-left-radius: 0.5rem;
}
td:last-of-type {
border-bottom-right-radius: 0.5rem;
}
}
}
.expanded {
padding: 0.25rem 1rem;
max-width: 100%;
border-left: 1px solid $text-color;
border-right: 1px solid $text-color;
border-bottom: 1px solid $text-color;
td {
white-space: normal;
word-break: break-all;
padding: 0.5rem 0.15rem;
width: 100%;
}
}
</style>

View File

@@ -0,0 +1,86 @@
<template>
<div>
<torrent-search-results
:query="query"
:tmdb_id="tmdb_id"
:class="{ truncated: truncated }"
><div
v-if="truncated"
class="load-more"
role="button"
@click="truncated = false"
>
<icon-arrow-down />
</div>
</torrent-search-results>
<div class="edit-query-btn-container">
<seasonedButton @click="openInTorrentPage"
>View on torrent page</seasonedButton
>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, inject, defineProps } from "vue";
import { useRouter } from "vue-router";
import TorrentSearchResults from "@/components/torrent/TorrentSearchResults.vue";
import SeasonedButton from "@/components/ui/SeasonedButton.vue";
import IconArrowDown from "@/icons/IconArrowDown.vue";
import type { Ref } from "vue";
interface Props {
query: string;
tmdb_id?: number;
}
const props = defineProps<Props>();
const router = useRouter();
const truncated: Ref<boolean> = ref(true);
function openInTorrentPage() {
if (!props.query?.length) {
router.push("/torrents");
return;
}
router.push({ path: "/torrents", query: { query: props.query } });
}
</script>
<style lang="scss" scoped>
:global(.truncated .torrent-table) {
position: relative;
max-height: 500px;
overflow-y: hidden;
}
.load-more {
position: absolute;
display: flex;
align-items: flex-end;
justify-content: center;
bottom: 0rem;
width: 100%;
height: 3rem;
cursor: pointer;
background: linear-gradient(
to top,
var(--background-color) 20%,
var(--background-0) 100%
);
}
svg {
height: 30px;
fill: var(--text-color);
}
.edit-query-btn-container {
display: flex;
justify-content: center;
padding: 1rem;
}
</style>