mirror of
https://github.com/KevinMidboe/seasoned.git
synced 2026-03-11 11:55:38 +00:00
Merge branch 'feat/vite' of github.com:kevinmidboe/seasoned into feat/vite
This commit is contained in:
18
.drone.yml
18
.drone.yml
@@ -25,7 +25,7 @@ steps:
|
||||
path: /cache
|
||||
|
||||
- name: Frontend install
|
||||
image: node:18.2.0
|
||||
image: node:24.13.1
|
||||
commands:
|
||||
- node -v
|
||||
- yarn --version
|
||||
@@ -42,8 +42,14 @@ steps:
|
||||
- name: cache
|
||||
path: /cache
|
||||
|
||||
- name: Lint project using eslint
|
||||
image: node:24.13.1
|
||||
commands:
|
||||
- yarn lint
|
||||
failure: ignore
|
||||
|
||||
- name: Frontend build
|
||||
image: node:18.2.0
|
||||
image: node:24.13.1
|
||||
commands:
|
||||
- yarn build
|
||||
environment:
|
||||
@@ -56,12 +62,6 @@ steps:
|
||||
SEASONED_DOMAIN:
|
||||
from_secret: SEASONED_DOMAIN
|
||||
|
||||
- name: Lint project using eslint
|
||||
image: node:18.2.0
|
||||
commands:
|
||||
- yarn lint
|
||||
failure: ignore
|
||||
|
||||
- name: Build and publish docker image
|
||||
image: plugins/docker
|
||||
settings:
|
||||
@@ -107,6 +107,6 @@ trigger:
|
||||
# - pull_request
|
||||
---
|
||||
kind: signature
|
||||
hmac: 8f76d7d8af65a215a4abafe7db270f93a4ae76d116db5ce6f1895af25354ff2d
|
||||
hmac: 6f10b2871d2bd6b5cd26ddf72796325991ba211ba1eb62b657baf993e9d549c8
|
||||
|
||||
...
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,6 +4,7 @@ src/config.json
|
||||
|
||||
# Build directory
|
||||
dist/
|
||||
lib/
|
||||
|
||||
# Node packages
|
||||
node_modules/
|
||||
|
||||
76
src/api.ts
76
src/api.ts
@@ -1,5 +1,9 @@
|
||||
/* eslint-disable n/no-unsupported-features/node-builtins */
|
||||
import { IList, IMediaCredits, IPersonCredits } from "./interfaces/IList";
|
||||
import type {
|
||||
IRequestStatusResponse,
|
||||
IRequestSubmitResponse
|
||||
} from "./interfaces/IRequestResponse";
|
||||
|
||||
const API_HOSTNAME = import.meta.env.VITE_SEASONED_API;
|
||||
const ELASTIC_URL = import.meta.env.VITE_ELASTIC_URL;
|
||||
@@ -261,7 +265,7 @@ const addMagnet = (magnet: string, name: string, tmdbId: number | null) => {
|
||||
* @param {string} type Movie or show type
|
||||
* @returns {object} Success/Failure response
|
||||
*/
|
||||
const request = (id, type) => {
|
||||
const request = (id, type): Promise<IRequestSubmitResponse> => {
|
||||
const url = new URL("/api/v2/request", API_HOSTNAME);
|
||||
|
||||
const options = {
|
||||
@@ -284,18 +288,13 @@ const request = (id, type) => {
|
||||
* @param {string} type
|
||||
* @returns {object} Success/Failure response
|
||||
*/
|
||||
const getRequestStatus = (id, type = undefined) => {
|
||||
const getRequestStatus = (id, type = null): Promise<IRequestStatusResponse> => {
|
||||
const url = new URL("/api/v2/request", API_HOSTNAME);
|
||||
url.pathname = `${url.pathname}/${id.toString()}`;
|
||||
url.searchParams.append("type", type);
|
||||
|
||||
return fetch(url.href)
|
||||
.then(resp => {
|
||||
const { status } = resp;
|
||||
if (status === 200) return true;
|
||||
|
||||
return false;
|
||||
})
|
||||
.then(resp => resp.json())
|
||||
.catch(err => Promise.reject(err));
|
||||
};
|
||||
|
||||
@@ -437,9 +436,13 @@ const unlinkPlexAccount = () => {
|
||||
|
||||
// - - - User graphs - - -
|
||||
|
||||
const fetchGraphData = (urlPath, days, chartType) => {
|
||||
const fetchGraphData = async (
|
||||
urlPath: string,
|
||||
days: number,
|
||||
chartType: string
|
||||
) => {
|
||||
const url = new URL(`/api/v1/user/${urlPath}`, API_HOSTNAME);
|
||||
url.searchParams.append("days", days);
|
||||
url.searchParams.append("days", String(days));
|
||||
url.searchParams.append("y_axis", chartType);
|
||||
|
||||
return fetch(url.href).then(resp => {
|
||||
@@ -454,7 +457,7 @@ const fetchGraphData = (urlPath, days, chartType) => {
|
||||
|
||||
// - - - Random emoji - - -
|
||||
|
||||
const getEmoji = () => {
|
||||
const getEmoji = async () => {
|
||||
const url = new URL("/api/v1/emoji", API_HOSTNAME);
|
||||
|
||||
return fetch(url.href)
|
||||
@@ -475,28 +478,51 @@ const getEmoji = () => {
|
||||
* @param {string} query
|
||||
* @returns {object} List of movies and shows matching query
|
||||
*/
|
||||
|
||||
const elasticSearchMoviesAndShows = (query, count = 22) => {
|
||||
const url = new URL(`${ELASTIC_URL}/_search`);
|
||||
|
||||
const body = {
|
||||
sort: [{ popularity: { order: "desc" } }, "_score"],
|
||||
size: count,
|
||||
query: {
|
||||
bool: {
|
||||
should: [
|
||||
{
|
||||
match_phrase_prefix: {
|
||||
original_name: query
|
||||
}
|
||||
},
|
||||
{
|
||||
match_phrase_prefix: {
|
||||
original_title: query
|
||||
}
|
||||
}
|
||||
]
|
||||
multi_match: {
|
||||
query,
|
||||
fields: ["name", "original_title", "original_name"],
|
||||
type: "phrase_prefix",
|
||||
tie_breaker: 0.3
|
||||
}
|
||||
},
|
||||
size: count
|
||||
suggest: {
|
||||
text: query,
|
||||
"person-suggest": {
|
||||
prefix: query,
|
||||
completion: {
|
||||
field: "name.completion",
|
||||
fuzzy: {
|
||||
fuzziness: "AUTO"
|
||||
}
|
||||
}
|
||||
},
|
||||
"movie-suggest": {
|
||||
prefix: query,
|
||||
completion: {
|
||||
field: "original_title.completion",
|
||||
fuzzy: {
|
||||
fuzziness: "AUTO"
|
||||
}
|
||||
}
|
||||
},
|
||||
"show-suggest": {
|
||||
prefix: query,
|
||||
completion: {
|
||||
field: "original_name.completion",
|
||||
fuzzy: {
|
||||
fuzziness: "AUTO"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const options = {
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, computed } from "vue";
|
||||
import { computed } from "vue";
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -10,8 +10,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, defineEmits } from "vue";
|
||||
|
||||
interface Props {
|
||||
active?: boolean;
|
||||
disabled?: boolean;
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps } from "vue";
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
detail?: string | number;
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
--></template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps } from "vue";
|
||||
import LoaderHeightType from "../../interfaces/ILoader";
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -10,8 +10,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps } from "vue";
|
||||
|
||||
interface Props {
|
||||
count?: number;
|
||||
lineClass?: string;
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, defineEmits } from "vue";
|
||||
|
||||
interface Props {
|
||||
active?: boolean;
|
||||
fullWidth?: boolean;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, defineEmits } from "vue";
|
||||
import type {
|
||||
ErrorMessageTypes,
|
||||
IErrorMessage
|
||||
|
||||
@@ -13,8 +13,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, defineEmits } from "vue";
|
||||
|
||||
interface Props {
|
||||
options: string[];
|
||||
selected?: string;
|
||||
|
||||
@@ -12,6 +12,7 @@ export interface IAutocompleteSearchResults {
|
||||
timed_out: boolean;
|
||||
_shards: Shards;
|
||||
hits: Hits;
|
||||
suggest: Suggest;
|
||||
}
|
||||
|
||||
export interface Shards {
|
||||
@@ -36,6 +37,27 @@ export interface Hit {
|
||||
sort: number[];
|
||||
}
|
||||
|
||||
export interface Suggest {
|
||||
"movie-suggest": SuggestOptions[];
|
||||
"person-suggest": SuggestOptions[];
|
||||
"show-suggest": SuggestOptions[];
|
||||
}
|
||||
|
||||
export interface SuggestOptions {
|
||||
text: string;
|
||||
offset: number;
|
||||
length: number;
|
||||
options: Option[];
|
||||
}
|
||||
|
||||
export interface Option {
|
||||
text: string;
|
||||
_index: string;
|
||||
_id: string;
|
||||
_score: number;
|
||||
_source: Source;
|
||||
}
|
||||
|
||||
export enum Index {
|
||||
Movies = "movies",
|
||||
Shows = "shows"
|
||||
@@ -56,6 +78,8 @@ export interface Source {
|
||||
agent: Agent;
|
||||
original_title: string;
|
||||
original_name?: string;
|
||||
name?: string;
|
||||
type?: MediaTypes;
|
||||
}
|
||||
|
||||
export interface Agent {
|
||||
|
||||
19
src/interfaces/IRequestResponse.ts
Normal file
19
src/interfaces/IRequestResponse.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export interface Result {
|
||||
id: number;
|
||||
title: string;
|
||||
year: number;
|
||||
type: string;
|
||||
status: string;
|
||||
requested_date: Date;
|
||||
}
|
||||
|
||||
export interface IRequestStatusResponse {
|
||||
success: boolean;
|
||||
message?: string;
|
||||
result?: Result;
|
||||
}
|
||||
|
||||
export interface IRequestSubmitResponse {
|
||||
success: boolean;
|
||||
message: string;
|
||||
}
|
||||
@@ -28,7 +28,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, defineProps, onMounted } from "vue";
|
||||
import { ref, onMounted } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import type { Ref } from "vue";
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
export const sortableSize = (string: string): number => {
|
||||
const UNITS = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||
const UNITS = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||
const [numStr, unit] = string.split(" ");
|
||||
|
||||
if (UNITS.indexOf(unit) === -1) return null;
|
||||
|
||||
const exponent = UNITS.indexOf(unit) * 3;
|
||||
return Number(numStr) * exponent ** 10;
|
||||
const exponent = UNITS.indexOf(unit) * 3 + 4;
|
||||
return Math.floor(Number(numStr) * 10 ** exponent);
|
||||
};
|
||||
|
||||
export const parseJwt = (token: string) => {
|
||||
|
||||
Reference in New Issue
Block a user