mirror of
https://github.com/KevinMidboe/seasoned.git
synced 2026-03-11 03:49:07 +00:00
Resolved ALL eslint issues for project
This commit is contained in:
@@ -1,13 +1,14 @@
|
||||
<template>
|
||||
<div class="wrapper" v-if="plexId">
|
||||
<div v-if="plexId" class="wrapper">
|
||||
<h1>Your watch activity</h1>
|
||||
|
||||
<div style="display: flex; flex-direction: row">
|
||||
<label class="filter">
|
||||
<label class="filter" for="dayinput">
|
||||
<span>Days:</span>
|
||||
<input
|
||||
class="dayinput"
|
||||
id="dayinput"
|
||||
v-model="days"
|
||||
class="dayinput"
|
||||
placeholder="days"
|
||||
type="number"
|
||||
pattern="[0-9]*"
|
||||
@@ -15,15 +16,15 @@
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label class="filter">
|
||||
<div class="filter">
|
||||
<span>Data sorted by:</span>
|
||||
<toggle-button
|
||||
v-model:selected="graphViewMode"
|
||||
class="filter-item"
|
||||
:options="[GraphTypes.Plays, GraphTypes.Duration]"
|
||||
v-model:selected="graphViewMode"
|
||||
@change="fetchChartData"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chart-section">
|
||||
@@ -34,9 +35,9 @@
|
||||
:data="playsByDayData"
|
||||
type="line"
|
||||
:stacked="false"
|
||||
:datasetDescriptionSuffix="`watch last ${days} days`"
|
||||
:tooltipDescriptionSuffix="selectedGraphViewMode.tooltipLabel"
|
||||
:graphValueType="selectedGraphViewMode.valueType"
|
||||
:dataset-description-suffix="`watch last ${days} days`"
|
||||
:tooltip-description-suffix="selectedGraphViewMode.tooltipLabel"
|
||||
:graph-value-type="selectedGraphViewMode.valueType"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -44,13 +45,12 @@
|
||||
<div class="graph">
|
||||
<Graph
|
||||
v-if="playsByDayofweekData"
|
||||
class="graph"
|
||||
:data="playsByDayofweekData"
|
||||
type="bar"
|
||||
:stacked="true"
|
||||
:datasetDescriptionSuffix="`watch last ${days} days`"
|
||||
:tooltipDescriptionSuffix="selectedGraphViewMode.tooltipLabel"
|
||||
:graphValueType="selectedGraphViewMode.valueType"
|
||||
:dataset-description-suffix="`watch last ${days} days`"
|
||||
:tooltip-description-suffix="selectedGraphViewMode.tooltipLabel"
|
||||
:graph-value-type="selectedGraphViewMode.valueType"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -61,23 +61,18 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { ref, computed } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import Graph from "@/components/Graph.vue";
|
||||
import ToggleButton from "@/components/ui/ToggleButton.vue";
|
||||
import IconStop from "@/icons/IconStop.vue";
|
||||
import { fetchGraphData } from "../api";
|
||||
import type { Ref } from "vue";
|
||||
|
||||
enum GraphTypes {
|
||||
Plays = "plays",
|
||||
Duration = "duration"
|
||||
}
|
||||
|
||||
enum GraphValueTypes {
|
||||
Number = "number",
|
||||
Time = "time"
|
||||
}
|
||||
import { fetchGraphData } from "../api";
|
||||
import {
|
||||
GraphTypes,
|
||||
GraphValueTypes,
|
||||
IGraphData
|
||||
} from "../interfaces/IGraph";
|
||||
|
||||
const store = useStore();
|
||||
|
||||
@@ -85,9 +80,6 @@
|
||||
const graphViewMode: Ref<GraphTypes> = ref(GraphTypes.Plays);
|
||||
const plexId = computed(() => store.getters["user/plexId"]);
|
||||
|
||||
const selectedGraphViewMode = computed(() =>
|
||||
graphValueViewMode.find(viewMode => viewMode.type === graphViewMode.value)
|
||||
);
|
||||
const graphValueViewMode = [
|
||||
{
|
||||
type: GraphTypes.Plays,
|
||||
@@ -101,13 +93,27 @@
|
||||
}
|
||||
];
|
||||
|
||||
const playsByDayData: Ref<any> = ref(null);
|
||||
const playsByDayofweekData: Ref<any> = ref(null);
|
||||
fetchChartData();
|
||||
const playsByDayData: Ref<IGraphData> = ref(null);
|
||||
const playsByDayofweekData: Ref<IGraphData> = ref(null);
|
||||
|
||||
function fetchChartData() {
|
||||
fetchPlaysByDay();
|
||||
fetchPlaysByDayOfWeek();
|
||||
const selectedGraphViewMode = computed(() =>
|
||||
graphValueViewMode.find(viewMode => viewMode.type === graphViewMode.value)
|
||||
);
|
||||
|
||||
function convertDateStringToDayMonth(date: string): string {
|
||||
if (!date.match(/[0-9]{4}-[0-9]{2}-[0-9]{2}/)) {
|
||||
return date;
|
||||
}
|
||||
|
||||
const [, month, day] = date.split("-");
|
||||
return `${day}.${month}`;
|
||||
}
|
||||
|
||||
function convertDateLabels(data) {
|
||||
return {
|
||||
labels: data.categories.map(convertDateStringToDayMonth),
|
||||
series: data.series
|
||||
};
|
||||
}
|
||||
|
||||
async function fetchPlaysByDay() {
|
||||
@@ -126,21 +132,12 @@
|
||||
).then(data => convertDateLabels(data?.data));
|
||||
}
|
||||
|
||||
function convertDateLabels(data) {
|
||||
return {
|
||||
labels: data.categories.map(convertDateStringToDayMonth),
|
||||
series: data.series
|
||||
};
|
||||
function fetchChartData() {
|
||||
fetchPlaysByDay();
|
||||
fetchPlaysByDayOfWeek();
|
||||
}
|
||||
|
||||
function convertDateStringToDayMonth(date: string): string {
|
||||
if (!date.match(/[0-9]{4}-[0-9]{2}-[0-9]{2}/)) {
|
||||
return date;
|
||||
}
|
||||
|
||||
const [year, month, day] = date.split("-");
|
||||
return `${day}.${month}`;
|
||||
}
|
||||
fetchChartData();
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<ResultsSection :title="listName" :apiFunction="_getTmdbMovieListByName" />
|
||||
<ResultsSection :title="listName" :api-function="_getTmdbMovieListByName" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<section class="profile">
|
||||
<div class="profile__content" v-if="loggedIn">
|
||||
<div v-if="loggedIn" class="profile__content">
|
||||
<header class="profile__header">
|
||||
<h2 class="profile__title">{{ emoji }} Welcome {{ username }}</h2>
|
||||
|
||||
<div class="button--group">
|
||||
<seasoned-button @click="toggleSettings" :active="showSettings">{{
|
||||
<seasoned-button :active="showSettings" @click="toggleSettings">{{
|
||||
showSettings ? "hide settings" : "show settings"
|
||||
}}</seasoned-button>
|
||||
<seasoned-button @click="toggleActivity" :active="showActivity">{{
|
||||
<seasoned-button :active="showActivity" @click="toggleActivity">{{
|
||||
showActivity ? "hide activity" : "show activity"
|
||||
}}</seasoned-button>
|
||||
|
||||
@@ -16,15 +16,15 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<settings v-if="showSettings" />
|
||||
<settings-page v-if="showSettings" />
|
||||
|
||||
<activity v-if="showActivity" />
|
||||
<activity-page v-if="showActivity" />
|
||||
|
||||
<page-header title="Your requests" :info="resultCount" />
|
||||
<results-list v-if="results" :results="results" />
|
||||
</div>
|
||||
|
||||
<section class="not-found" v-if="!loggedIn">
|
||||
<section v-if="!loggedIn" class="not-found">
|
||||
<div class="not-found__content">
|
||||
<h2 class="not-found__title">Authentication Request Failed</h2>
|
||||
<router-link :to="{ name: 'signin' }" exact title="Sign in here">
|
||||
@@ -40,11 +40,11 @@
|
||||
import { useStore } from "vuex";
|
||||
import PageHeader from "@/components/PageHeader.vue";
|
||||
import ResultsList from "@/components/ResultsList.vue";
|
||||
import Settings from "@/pages/SettingsPage.vue";
|
||||
import Activity from "@/pages/ActivityPage.vue";
|
||||
import SettingsPage from "@/pages/SettingsPage.vue";
|
||||
import ActivityPage from "@/pages/ActivityPage.vue";
|
||||
import SeasonedButton from "@/components/ui/SeasonedButton.vue";
|
||||
import { getEmoji, getUserRequests, getSettings, logout } from "../api";
|
||||
import type { Ref, ComputedRef } from "vue";
|
||||
import { getEmoji, getUserRequests } from "../api";
|
||||
import type { ListResults } from "../interfaces/IList";
|
||||
|
||||
const emoji: Ref<string> = ref("");
|
||||
@@ -57,7 +57,6 @@
|
||||
|
||||
const loggedIn: Ref<boolean> = computed(() => store.getters["user/loggedIn"]);
|
||||
const username: Ref<string> = computed(() => store.getters["user/username"]);
|
||||
const settings: Ref<object> = computed(() => store.getters["user/settings"]);
|
||||
|
||||
const resultCount: ComputedRef<number | string> = computed(() => {
|
||||
const currentCount = results?.value?.length || 0;
|
||||
@@ -65,6 +64,10 @@
|
||||
return `${currentCount} of ${totalCount} results`;
|
||||
});
|
||||
|
||||
function setEmoji(_emoji: string) {
|
||||
emoji.value = _emoji;
|
||||
}
|
||||
|
||||
// Component loaded actions
|
||||
getUserRequests().then(requestResults => {
|
||||
if (!requestResults?.results) return;
|
||||
@@ -72,26 +75,12 @@
|
||||
totalResults.value = requestResults.total_results;
|
||||
});
|
||||
|
||||
getEmoji().then(resp => (emoji.value = resp?.emoji));
|
||||
getEmoji().then(resp => setEmoji(resp?.emoji));
|
||||
|
||||
showSettings.value = window.location.toString().includes("settings=true");
|
||||
showActivity.value = window.location.toString().includes("activity=true");
|
||||
// Component loaded actions end
|
||||
|
||||
function toggleSettings() {
|
||||
showSettings.value = !showSettings.value;
|
||||
updateQueryParams("settings", showSettings.value);
|
||||
}
|
||||
|
||||
function toggleActivity() {
|
||||
showActivity.value = !showActivity.value;
|
||||
updateQueryParams("activity", showActivity.value);
|
||||
}
|
||||
|
||||
function _logout() {
|
||||
store.dispatch("user/logout");
|
||||
}
|
||||
|
||||
function updateQueryParams(key, value = false) {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
if (params.has(key)) {
|
||||
@@ -112,6 +101,20 @@
|
||||
}`
|
||||
);
|
||||
}
|
||||
|
||||
function toggleSettings() {
|
||||
showSettings.value = !showSettings.value;
|
||||
updateQueryParams("settings", showSettings.value);
|
||||
}
|
||||
|
||||
function toggleActivity() {
|
||||
showActivity.value = !showActivity.value;
|
||||
updateQueryParams("activity", showActivity.value);
|
||||
}
|
||||
|
||||
function _logout() {
|
||||
store.dispatch("user/logout");
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -2,27 +2,27 @@
|
||||
<section>
|
||||
<h1>Register new user</h1>
|
||||
|
||||
<form class="form" ref="formElement">
|
||||
<form ref="formElement" class="form">
|
||||
<seasoned-input
|
||||
v-model="username"
|
||||
placeholder="username"
|
||||
icon="Email"
|
||||
type="email"
|
||||
v-model="username"
|
||||
@keydown.enter="focusOnNextElement"
|
||||
/>
|
||||
|
||||
<seasoned-input
|
||||
v-model="password"
|
||||
placeholder="password"
|
||||
icon="Keyhole"
|
||||
type="password"
|
||||
v-model="password"
|
||||
@keydown.enter="focusOnNextElement"
|
||||
/>
|
||||
<seasoned-input
|
||||
v-model="passwordRepeat"
|
||||
placeholder="repeat password"
|
||||
icon="Keyhole"
|
||||
type="password"
|
||||
v-model="passwordRepeat"
|
||||
@keydown.enter="submit"
|
||||
/>
|
||||
|
||||
@@ -44,10 +44,10 @@
|
||||
import SeasonedButton from "@/components/ui/SeasonedButton.vue";
|
||||
import SeasonedInput from "@/components/ui/SeasonedInput.vue";
|
||||
import SeasonedMessages from "@/components/ui/SeasonedMessages.vue";
|
||||
import type { Ref } from "vue";
|
||||
import { register } from "../api";
|
||||
import { focusFirstFormInput, focusOnNextElement } from "../utils";
|
||||
import { ErrorMessageTypes } from "../interfaces/IErrorMessage";
|
||||
import type { Ref } from "vue";
|
||||
import type { IErrorMessage } from "../interfaces/IErrorMessage";
|
||||
|
||||
const username: Ref<string> = ref("");
|
||||
@@ -85,32 +85,27 @@
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!username.value || username?.value?.length === 0) {
|
||||
addWarningMessage("Missing username", "Validation error");
|
||||
return reject();
|
||||
reject();
|
||||
}
|
||||
|
||||
if (!password.value || password?.value?.length === 0) {
|
||||
addWarningMessage("Missing password", "Validation error");
|
||||
return reject();
|
||||
reject();
|
||||
}
|
||||
|
||||
if (passwordRepeat.value == null || passwordRepeat.value.length == 0) {
|
||||
if (passwordRepeat.value == null || passwordRepeat.value.length === 0) {
|
||||
addWarningMessage("Missing repeat password", "Validation error");
|
||||
return reject();
|
||||
reject();
|
||||
}
|
||||
if (passwordRepeat != password) {
|
||||
if (passwordRepeat.value !== password.value) {
|
||||
addWarningMessage("Passwords do not match", "Validation error");
|
||||
return reject();
|
||||
reject();
|
||||
}
|
||||
|
||||
resolve(true);
|
||||
});
|
||||
}
|
||||
|
||||
function submit() {
|
||||
clearMessages();
|
||||
validate().then(registerUser);
|
||||
}
|
||||
|
||||
function registerUser() {
|
||||
register(username.value, password.value)
|
||||
.then(data => {
|
||||
@@ -120,15 +115,19 @@
|
||||
})
|
||||
.catch(error => {
|
||||
if (error?.status === 401) {
|
||||
return addErrorMessage(
|
||||
"Incorrect username or password",
|
||||
"Access denied"
|
||||
);
|
||||
addErrorMessage("Incorrect username or password", "Access denied");
|
||||
return null;
|
||||
}
|
||||
|
||||
addErrorMessage(error?.message, "Unexpected error");
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
function submit() {
|
||||
clearMessages();
|
||||
validate().then(registerUser);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<ResultsSection title="Requests" :apiFunction="getRequests" />
|
||||
<ResultsSection title="Requests" :api-function="getRequests" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
<template>
|
||||
<div>
|
||||
<div style="display: flex; flex-direction: row">
|
||||
<label class="filter">
|
||||
<div class="filter">
|
||||
<span>Search filter:</span>
|
||||
|
||||
<toggle-button
|
||||
:options="toggleOptions"
|
||||
v-model:selected="mediaType"
|
||||
:options="toggleOptions"
|
||||
@change="toggleChanged"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ResultsSection v-if="query" :title="title" :apiFunction="search" />
|
||||
<ResultsSection v-if="query" :title="title" :api-function="search" />
|
||||
<h1 v-else class="no-results">No query found, please search above</h1>
|
||||
</div>
|
||||
</template>
|
||||
@@ -20,12 +20,11 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { searchTmdb } from "../api";
|
||||
|
||||
import ResultsSection from "@/components/ResultsSection.vue";
|
||||
import PageHeader from "@/components/PageHeader.vue";
|
||||
import ToggleButton from "@/components/ui/ToggleButton.vue";
|
||||
import type { Ref } from "vue";
|
||||
import { searchTmdb } from "../api";
|
||||
import { MediaTypes } from "../interfaces/IList";
|
||||
|
||||
// interface ISearchParams {
|
||||
@@ -54,20 +53,14 @@
|
||||
mediaType.value = (urlQuery?.media_type as MediaTypes) || mediaType.value;
|
||||
}
|
||||
|
||||
let search = (
|
||||
const search = (
|
||||
_page = page.value || 1,
|
||||
_mediaType = mediaType.value || "all"
|
||||
) => {
|
||||
return searchTmdb(query.value, _page, adult.value, _mediaType);
|
||||
};
|
||||
|
||||
function toggleChanged() {
|
||||
updateQueryParams();
|
||||
}
|
||||
|
||||
function updateQueryParams() {
|
||||
const { query, page, adult, media_type } = route.query;
|
||||
|
||||
router.push({
|
||||
path: "search",
|
||||
query: {
|
||||
@@ -76,6 +69,10 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function toggleChanged() {
|
||||
updateQueryParams();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -11,12 +11,28 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { inject } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import { useRoute } from "vue-router";
|
||||
import ChangePassword from "@/components/profile/ChangePassword.vue";
|
||||
import LinkPlexAccount from "@/components/profile/LinkPlexAccount.vue";
|
||||
import { getSettings } from "../api";
|
||||
|
||||
const store = useStore();
|
||||
const route = useRoute();
|
||||
const notifications: {
|
||||
error;
|
||||
} = inject("notifications");
|
||||
|
||||
function displayWarningIfMissingPlexAccount() {
|
||||
if (route.query?.missingPlexAccount === "true") {
|
||||
notifications.error({
|
||||
title: "Missing plex account 🧲",
|
||||
description: "Link your plex account to view activity",
|
||||
timeout: 10000
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function reloadSettings() {
|
||||
return getSettings().then(response => {
|
||||
@@ -26,6 +42,9 @@
|
||||
store.dispatch("user/setSettings", settings);
|
||||
});
|
||||
}
|
||||
|
||||
// Functions called on component load
|
||||
displayWarningIfMissingPlexAccount();
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -2,19 +2,19 @@
|
||||
<section>
|
||||
<h1>Sign in</h1>
|
||||
|
||||
<form class="form" ref="formElement">
|
||||
<form ref="formElement" class="form">
|
||||
<seasoned-input
|
||||
v-model="username"
|
||||
placeholder="username"
|
||||
icon="Email"
|
||||
type="email"
|
||||
v-model="username"
|
||||
@keydown.enter="focusOnNextElement"
|
||||
/>
|
||||
<seasoned-input
|
||||
v-model="password"
|
||||
placeholder="password"
|
||||
icon="Keyhole"
|
||||
type="password"
|
||||
v-model="password"
|
||||
@keydown.enter="submit"
|
||||
/>
|
||||
|
||||
@@ -35,10 +35,10 @@
|
||||
import SeasonedInput from "@/components/ui/SeasonedInput.vue";
|
||||
import SeasonedButton from "@/components/ui/SeasonedButton.vue";
|
||||
import SeasonedMessages from "@/components/ui/SeasonedMessages.vue";
|
||||
import type { Ref } from "vue";
|
||||
import { login } from "../api";
|
||||
import { focusFirstFormInput, focusOnNextElement } from "../utils";
|
||||
import { ErrorMessageTypes } from "../interfaces/IErrorMessage";
|
||||
import type { Ref } from "vue";
|
||||
import type { IErrorMessage } from "../interfaces/IErrorMessage";
|
||||
|
||||
const username: Ref<string> = ref("");
|
||||
@@ -75,23 +75,18 @@
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!username.value || username?.value?.length === 0) {
|
||||
addWarningMessage("Missing username", "Validation error");
|
||||
return reject();
|
||||
reject();
|
||||
}
|
||||
|
||||
if (!password.value || password?.value?.length === 0) {
|
||||
addWarningMessage("Missing password", "Validation error");
|
||||
return reject();
|
||||
reject();
|
||||
}
|
||||
|
||||
resolve(true);
|
||||
});
|
||||
}
|
||||
|
||||
function submit() {
|
||||
clearMessages();
|
||||
validate().then(signin);
|
||||
}
|
||||
|
||||
function signin() {
|
||||
login(username.value, password.value, true)
|
||||
.then(data => {
|
||||
@@ -101,15 +96,19 @@
|
||||
})
|
||||
.catch(error => {
|
||||
if (error?.status === 401) {
|
||||
return addErrorMessage(
|
||||
"Incorrect username or password",
|
||||
"Access denied"
|
||||
);
|
||||
addErrorMessage("Incorrect username or password", "Access denied");
|
||||
return null;
|
||||
}
|
||||
|
||||
addErrorMessage(error?.message, "Unexpected error");
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
function submit() {
|
||||
clearMessages();
|
||||
validate().then(signin);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
<seasoned-input
|
||||
v-model="query"
|
||||
type="torrents"
|
||||
@keydown.enter="setTorrentQuery"
|
||||
placeholder="Search torrents"
|
||||
@keydown.enter="setTorrentQuery"
|
||||
/>
|
||||
<seasoned-button @click="setTorrentQuery">Search</seasoned-button>
|
||||
</div>
|
||||
@@ -27,8 +27,8 @@
|
||||
import SeasonedButton from "@/components/ui/SeasonedButton.vue";
|
||||
import TorrentList from "@/components/torrent/TorrentSearchResults.vue";
|
||||
import ActiveTorrents from "@/components/torrent/ActiveTorrents.vue";
|
||||
import { getValueFromUrlQuery, setUrlQueryParameter } from "../utils";
|
||||
import type { Ref } from "vue";
|
||||
import { getValueFromUrlQuery, setUrlQueryParameter } from "../utils";
|
||||
|
||||
const urlQuery = getValueFromUrlQuery("query");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user