Merge branch 'master' into feat/vite

This commit is contained in:
2026-02-23 20:44:55 +01:00
committed by GitHub
28 changed files with 207 additions and 98 deletions

View File

@@ -9,7 +9,7 @@
</template>
<script setup lang="ts">
import { defineProps, computed } from "vue";
import { computed } from "vue";
import { useStore } from "vuex";
import type { ICast, ICrew, IMovie, IShow } from "../interfaces/IList";

View File

@@ -3,7 +3,7 @@
</template>
<script setup lang="ts">
import { ref, defineProps, onMounted, watch } from "vue";
import { ref, onMounted, watch } from "vue";
import {
Chart,
LineElement,

View File

@@ -23,7 +23,7 @@
</template>
<script setup lang="ts">
import { defineProps, computed } from "vue";
import { computed } from "vue";
interface Props {
title: string;

View File

@@ -17,7 +17,6 @@
</template>
<script setup lang="ts">
import { defineProps } from "vue";
import ResultsListItem from "@/components/ResultsListItem.vue";
import type { ListResults } from "../interfaces/IList";

View File

@@ -35,7 +35,7 @@
</template>
<script setup lang="ts">
import { ref, computed, defineProps, onMounted } from "vue";
import { ref, computed, onMounted } from "vue";
import { useStore } from "vuex";
import type { Ref } from "vue";
import type { IMovie, IShow, IPerson } from "../interfaces/IList";

View File

@@ -27,7 +27,7 @@
</template>
<script setup lang="ts">
import { defineProps, ref, computed, onMounted } from "vue";
import { ref, computed, onMounted } from "vue";
import PageHeader from "@/components/PageHeader.vue";
import ResultsList from "@/components/ResultsList.vue";
import SeasonedButton from "@/components/ui/SeasonedButton.vue";

View File

@@ -10,6 +10,7 @@
>
<IconMovie v-if="result.type == 'movie'" class="type-icon" />
<IconShow v-if="result.type == 'show'" class="type-icon" />
<IconPerson v-if="result.type == 'person'" class="type-icon" />
<span class="title">{{ result.title }}</span>
</li>
@@ -23,6 +24,10 @@
</transition>
</template>
<!--
Searches Elasticsearch for results based on changes to `query`.
-->
<script setup lang="ts">
import type { Ref } from "vue";
import { ref, watch, defineProps } from "vue";
@@ -31,10 +36,12 @@
import IconShow from "../../icons/IconShow.vue";
import { elasticSearchMoviesAndShows } from "../../api";
import { MediaTypes } from "../../interfaces/IList";
import { Index } from "../../interfaces/IAutocompleteSearch";
import type {
IAutocompleteResult,
IAutocompleteSearchResults
IAutocompleteSearchResults,
Hit,
Option,
Source
} from "../../interfaces/IAutocompleteSearch";
interface Props {
@@ -48,6 +55,7 @@
}
const numberOfResults = 10;
let timeoutId = null;
const props = defineProps<Props>();
const emit = defineEmits<Emit>();
const store = useStore();
@@ -55,23 +63,9 @@
const searchResults: Ref<Array<IAutocompleteResult>> = ref([]);
const keyboardNavigationIndex: Ref<number> = ref(0);
watch(
() => props.query,
newQuery => {
if (newQuery?.length > 0)
fetchAutocompleteResults(); /* eslint-disable-line no-use-before-define */
}
);
function openPopup(result) {
if (!result.id || !result.type) return;
store.dispatch("popup/open", { ...result });
}
function removeDuplicates(_searchResults) {
function removeDuplicates(_searchResults: Array<IAutocompleteResult>) {
const filteredResults = [];
_searchResults.forEach(result => {
_searchResults.forEach((result: IAutocompleteResult) => {
if (result === undefined) return;
const numberOfDuplicates = filteredResults.filter(
filterItem => filterItem.id === result.id
@@ -86,34 +80,43 @@
return filteredResults;
}
function elasticIndexToMediaType(index: Index): MediaTypes {
if (index === Index.Movies) return MediaTypes.Movie;
if (index === Index.Shows) return MediaTypes.Show;
function convertMediaType(type: string | null): MediaTypes | null {
if (type === "movie") return MediaTypes.Movie;
if (type === "tv_series") return MediaTypes.Show;
if (type === "person") return MediaTypes.Person;
return null;
}
function parseElasticResponse(elasticResponse: IAutocompleteSearchResults) {
const data = elasticResponse.hits.hits;
const elasticResults = elasticResponse.hits.hits;
const suggestResults = elasticResponse.suggest["movie-suggest"][0].options;
let data: Array<Source> = elasticResults.map((el: Hit) => el._source);
data = data.concat(suggestResults.map((el: Option) => el._source));
// data = data.concat(elasticResponse['suggest']['person-suggest'][0]['options'])
// data = data.concat(elasticResponse['suggest']['show-suggest'][0]['options'])
data = data.sort((a, b) => (a.popularity < b.popularity ? 1 : -1));
const results: Array<IAutocompleteResult> = [];
data.forEach(item => {
if (!Object.values(Index).includes(item._index)) {
return;
}
results.push({
title: item._source?.original_name || item._source.original_title,
id: item._source.id,
adult: item._source.adult,
type: elasticIndexToMediaType(item._index)
title: item?.original_name || item?.original_title || item?.name,
id: item.id,
adult: item.adult,
type: convertMediaType(item?.type)
});
});
return removeDuplicates(results).map((el, index) => {
return { ...el, index };
});
return removeDuplicates(results)
.map((el, index) => {
return { ...el, index };
})
.slice(0, 10);
}
function fetchAutocompleteResults() {
@@ -123,11 +126,34 @@
elasticSearchMoviesAndShows(props.query, numberOfResults)
.then(elasticResponse => parseElasticResponse(elasticResponse))
.then(_searchResults => {
console.log(_searchResults);
emit("update:results", _searchResults);
searchResults.value = _searchResults;
});
}
const debounce = (callback: () => void, wait: number) => {
window.clearTimeout(timeoutId);
timeoutId = window.setTimeout(() => {
callback();
}, wait);
};
watch(
() => props.query,
newQuery => {
if (newQuery?.length > 0) {
debounce(fetchAutocompleteResults, 150);
}
}
);
function openPopup(result: IAutocompleteResult) {
if (!result.id || !result.type) return;
store.dispatch("popup/open", { ...result });
}
// on load functions
fetchAutocompleteResults();
// end on load functions

View File

@@ -16,7 +16,7 @@
<script setup lang="ts">
import { useStore } from "vuex";
import { computed, defineProps } from "vue";
import { computed } from "vue";
import type INavigationIcon from "../../interfaces/INavigationIcon";
interface Props {

View File

@@ -42,6 +42,18 @@
</div>
</template>
<!-- Handles constructing markup and state for dropdown.
Markup:
Consist of: search icon, input & close button.
State:
State is passing input variable `query` to dropdown and carrying state
of selected dropdown element as variable `index`. This is because
index is manipulated based on arrow key events from same input as
the `query`.
-->
<script setup lang="ts">
import type { Ref } from "vue";
import { ref, computed } from "vue";
@@ -51,6 +63,7 @@
import IconSearch from "../../icons/IconSearch.vue";
import IconClose from "../../icons/IconClose.vue";
import type { MediaTypes } from "../../interfaces/IList";
import { IAutocompleteResult } from "../../interfaces/IAutocompleteSearch";
interface ISearchResult {
title: string;
@@ -66,7 +79,7 @@
const query: Ref<string> = ref(null);
const disabled: Ref<boolean> = ref(false);
const dropdownIndex: Ref<number> = ref(-1);
const dropdownResults: Ref<ISearchResult[]> = ref([]);
const dropdownResults: Ref<IAutocompleteResult[]> = ref([]);
const inputIsActive: Ref<boolean> = ref(false);
const inputElement: Ref<HTMLInputElement> = ref(null);
@@ -146,6 +159,7 @@
function handleSubmit() {
if (!query.value || query.value.length === 0) return;
// if index is set, navigation has happened. Open popup else search
if (dropdownIndex.value >= 0) {
const resultItem = dropdownResults.value[dropdownIndex.value];

View File

@@ -10,8 +10,6 @@
</template>
<script setup lang="ts">
import { defineProps, defineEmits } from "vue";
interface Props {
active?: boolean;
disabled?: boolean;

View File

@@ -14,7 +14,7 @@
</template>
<script setup lang="ts">
import { ref, defineProps, onMounted } from "vue";
import { ref, onMounted } from "vue";
import type { Ref } from "vue";
import IconArrowDown from "../../icons/IconArrowDown.vue";

View File

@@ -8,8 +8,6 @@
</template>
<script setup lang="ts">
import { defineProps } from "vue";
interface Props {
title: string;
detail?: string | number;

View File

@@ -165,7 +165,7 @@
</template>
<script setup lang="ts">
import { ref, computed, defineProps, onMounted } from "vue";
import { ref, computed, onMounted } from "vue";
import { useStore } from "vuex";
// import img from "@/directives/v-image";
@@ -189,6 +189,10 @@
IMediaCredits,
ICast
} from "../../interfaces/IList";
import type {
IRequestStatusResponse,
IRequestSubmitResponse
} from "../../interfaces/IRequestResponse";
import { MediaTypes } from "../../interfaces/IList";
import { humanMinutes } from "../../utils";
@@ -240,8 +244,15 @@
function setCast(_cast: ICast[]) {
cast.value = _cast;
}
function setRequested(status: boolean) {
requested.value = status;
function setRequested(
requestResponse: IRequestStatusResponse | IRequestSubmitResponse
) {
if (requestResponse?.success) {
requested.value = requestResponse?.success;
return;
}
requested.value = false;
}
function setBackdrop(): void {
@@ -300,13 +311,13 @@
.then(() => getCredits(props.type))
.then(credits => setCast(credits?.cast || []))
.then(() => getRequestStatus(props.id, props.type))
.then(requestStatus => setRequested(requestStatus || false))
.then(requestResponse => setRequested(requestResponse))
.then(setBackdrop);
}
function sendRequest() {
request(props.id, props.type).then(resp =>
setRequested(resp?.success || false)
request(props.id, props.type).then(requestResponse =>
setRequested(requestResponse)
);
}

View File

@@ -70,7 +70,7 @@
</template>
<script setup lang="ts">
import { ref, computed, defineProps } from "vue";
import { ref, computed } from "vue";
import CastList from "@/components/CastList.vue";
import Detail from "@/components/popup/Detail.vue";
import Description from "@/components/popup/Description.vue";

View File

@@ -43,7 +43,7 @@
</template>
<script setup lang="ts">
import { ref, computed, defineEmits } from "vue";
import { ref, computed } from "vue";
import { useStore } from "vuex";
import seasonedInput from "@/components/ui/SeasonedInput.vue";
import SeasonedButton from "@/components/ui/SeasonedButton.vue";

View File

@@ -20,7 +20,7 @@
</template>
<script setup lang="ts">
import { ref, inject, defineProps } from "vue";
import { ref, watch, inject } from "vue";
import { useStore } from "vuex";
import Loader from "@/components/ui/Loader.vue";
import TorrentTable from "@/components/torrent/TorrentTable.vue";
@@ -96,6 +96,8 @@
});
}
watch(props, newValue => newValue?.query?.length && fetchTorrents());
fetchTorrents();
</script>

View File

@@ -52,7 +52,7 @@
</template>
<script setup lang="ts">
import { ref, defineProps, defineEmits } from "vue";
import { ref } from "vue";
import IconMagnet from "@/icons/IconMagnet.vue";
import type { Ref } from "vue";
import { sortableSize } from "../../utils";
@@ -131,12 +131,12 @@
function sortSize() {
const torrentsCopy = [...torrents.value];
if (direction.value) {
torrents.value = torrentsCopy.sort(
(a, b) => sortableSize(a.size) - sortableSize(b.size)
torrents.value = torrentsCopy.sort((a, b) =>
sortableSize(a.size) > sortableSize(b.size) ? 1 : -1
);
} else {
torrents.value = torrentsCopy.sort(
(a, b) => sortableSize(b.size) - sortableSize(a.size)
torrents.value = torrentsCopy.sort((a, b) =>
sortableSize(a.size) < sortableSize(b.size) ? 1 : -1
);
}
}

View File

@@ -12,7 +12,6 @@
--></template>
<script setup lang="ts">
import { defineProps } from "vue";
import LoaderHeightType from "../../interfaces/ILoader";
interface Props {

View File

@@ -10,8 +10,6 @@
</template>
<script setup lang="ts">
import { defineProps } from "vue";
interface Props {
count?: number;
lineClass?: string;

View File

@@ -9,8 +9,6 @@
</template>
<script setup lang="ts">
import { defineProps, defineEmits } from "vue";
interface Props {
active?: boolean;
fullWidth?: boolean;

View File

@@ -26,7 +26,7 @@
</template>
<script setup lang="ts">
import { ref, computed, defineProps, defineEmits } from "vue";
import { ref, computed } from "vue";
import IconKey from "@/icons/IconKey.vue";
import IconEmail from "@/icons/IconEmail.vue";
import IconBinoculars from "@/icons/IconBinoculars.vue";

View File

@@ -22,7 +22,6 @@
</template>
<script setup lang="ts">
import { defineProps, defineEmits } from "vue";
import type {
ErrorMessageTypes,
IErrorMessage

View File

@@ -13,8 +13,6 @@
</template>
<script setup lang="ts">
import { defineProps, defineEmits } from "vue";
interface Props {
options: string[];
selected?: string;