Upgraded entries, plugins, router & webpack to vue 3 & typescript
This commit is contained in:
92
src/App.vue
92
src/App.vue
@@ -8,7 +8,10 @@
|
||||
</div>
|
||||
|
||||
<!-- Display the component assigned to the given route (default: home) -->
|
||||
<router-view class="content" :key="$route.fullPath"></router-view>
|
||||
<router-view
|
||||
class="content"
|
||||
:key="router.currentRoute.value.path"
|
||||
></router-view>
|
||||
|
||||
<!-- Popup that will show above existing rendered content -->
|
||||
<popup />
|
||||
@@ -17,61 +20,54 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NavigationHeader from "@/components/header/NavigationHeader";
|
||||
import NavigationIcons from "@/components/header/NavigationIcons";
|
||||
import Popup from "@/components/Popup";
|
||||
import DarkmodeToggle from "@/components/ui/DarkmodeToggle";
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from "vue-router";
|
||||
import NavigationHeader from "@/components/header/NavigationHeader.vue";
|
||||
import NavigationIcons from "@/components/header/NavigationIcons.vue";
|
||||
import Popup from "@/components/Popup.vue";
|
||||
import DarkmodeToggle from "@/components/ui/DarkmodeToggle.vue";
|
||||
|
||||
export default {
|
||||
name: "app",
|
||||
components: {
|
||||
NavigationHeader,
|
||||
NavigationIcons,
|
||||
Popup,
|
||||
DarkmodeToggle
|
||||
}
|
||||
};
|
||||
const router = useRouter();
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "src/scss/main";
|
||||
@import "src/scss/media-queries";
|
||||
@import "src/scss/main";
|
||||
@import "src/scss/media-queries";
|
||||
|
||||
#app {
|
||||
display: grid;
|
||||
grid-template-rows: var(--header-size);
|
||||
grid-template-columns: var(--header-size) 1fr;
|
||||
|
||||
@include mobile {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: 15;
|
||||
}
|
||||
|
||||
.navigation-icons-gutter {
|
||||
position: fixed;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
top: var(--header-size);
|
||||
width: var(--header-size);
|
||||
background-color: var(--background-color-secondary);
|
||||
}
|
||||
|
||||
.content {
|
||||
#app {
|
||||
display: grid;
|
||||
grid-column: 2 / 3;
|
||||
grid-row: 2;
|
||||
z-index: 5;
|
||||
grid-template-rows: var(--header-size);
|
||||
grid-template-columns: var(--header-size) 1fr;
|
||||
|
||||
@include mobile {
|
||||
grid-column: 1 / 3;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: 15;
|
||||
}
|
||||
|
||||
.navigation-icons-gutter {
|
||||
position: fixed;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
top: var(--header-size);
|
||||
width: var(--header-size);
|
||||
background-color: var(--background-color-secondary);
|
||||
}
|
||||
|
||||
.content {
|
||||
display: grid;
|
||||
grid-column: 2 / 3;
|
||||
grid-row: 2;
|
||||
z-index: 5;
|
||||
|
||||
@include mobile {
|
||||
grid-column: 1 / 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
27
src/api.ts
27
src/api.ts
@@ -1,5 +1,5 @@
|
||||
import config from "./config";
|
||||
import { IList } from "./interfaces/IList";
|
||||
import { IList, IMediaCredits, IPersonCredits } from "./interfaces/IList";
|
||||
|
||||
let { SEASONED_URL, ELASTIC_URL, ELASTIC_INDEX } = config;
|
||||
if (!SEASONED_URL) {
|
||||
@@ -99,24 +99,12 @@ const getPerson = (id, credits = false) => {
|
||||
});
|
||||
};
|
||||
|
||||
const getCredits = (type, id) => {
|
||||
if (type === "movie") {
|
||||
return getMovieCredits(id);
|
||||
} else if (type === "show") {
|
||||
return getShowCredits(id);
|
||||
} else if (type === "person") {
|
||||
return getPersonCredits(id);
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches tmdb movie credits by id.
|
||||
* @param {number} id
|
||||
* @returns {object} Tmdb response
|
||||
*/
|
||||
const getMovieCredits = id => {
|
||||
const getMovieCredits = (id: number): Promise<IMediaCredits> => {
|
||||
const url = new URL("/api/v2/movie", SEASONED_URL);
|
||||
url.pathname = `${url.pathname}/${id.toString()}/credits`;
|
||||
|
||||
@@ -133,7 +121,7 @@ const getMovieCredits = id => {
|
||||
* @param {number} id
|
||||
* @returns {object} Tmdb response
|
||||
*/
|
||||
const getShowCredits = id => {
|
||||
const getShowCredits = (id: number): Promise<IMediaCredits> => {
|
||||
const url = new URL("/api/v2/show", SEASONED_URL);
|
||||
url.pathname = `${url.pathname}/${id.toString()}/credits`;
|
||||
|
||||
@@ -150,7 +138,7 @@ const getShowCredits = id => {
|
||||
* @param {number} id
|
||||
* @returns {object} Tmdb response
|
||||
*/
|
||||
const getPersonCredits = id => {
|
||||
const getPersonCredits = (id: number): Promise<IPersonCredits> => {
|
||||
const url = new URL("/api/v2/person", SEASONED_URL);
|
||||
url.pathname = `${url.pathname}/${id.toString()}/credits`;
|
||||
|
||||
@@ -250,7 +238,7 @@ const searchTorrents = query => {
|
||||
* @param {boolean} tmdb_id
|
||||
* @returns {object} Success/Failure response
|
||||
*/
|
||||
const addMagnet = (magnet, name, tmdb_id) => {
|
||||
const addMagnet = (magnet: string, name: string, tmdb_id: number | null) => {
|
||||
const url = new URL("/api/v1/pirate/add", SEASONED_URL);
|
||||
|
||||
const options = {
|
||||
@@ -438,7 +426,7 @@ const linkPlexAccount = (username, password) => {
|
||||
});
|
||||
};
|
||||
|
||||
const unlinkPlexAccount = (username, password) => {
|
||||
const unlinkPlexAccount = () => {
|
||||
const url = new URL("/api/v1/user/unlink_plex", SEASONED_URL);
|
||||
|
||||
const options = {
|
||||
@@ -449,7 +437,7 @@ const unlinkPlexAccount = (username, password) => {
|
||||
return fetch(url.href, options)
|
||||
.then(resp => resp.json())
|
||||
.catch(error => {
|
||||
console.error(`api error unlinking plex account: ${username}`);
|
||||
console.error(`api error unlinking your plex account`);
|
||||
throw error;
|
||||
});
|
||||
};
|
||||
@@ -539,7 +527,6 @@ export {
|
||||
getMovieCredits,
|
||||
getShowCredits,
|
||||
getPersonCredits,
|
||||
getCredits,
|
||||
getTmdbMovieListByName,
|
||||
searchTmdb,
|
||||
getUserRequests,
|
||||
|
||||
21
src/main.js
21
src/main.js
@@ -1,21 +0,0 @@
|
||||
import Vue from "vue";
|
||||
import VueRouter from "vue-router";
|
||||
import router from "./routes";
|
||||
import store from "./store";
|
||||
|
||||
import Toast from "./plugins/Toast";
|
||||
import App from "./App.vue";
|
||||
|
||||
Vue.use(VueRouter);
|
||||
Vue.use(Toast);
|
||||
|
||||
store.dispatch("darkmodeModule/findAndSetDarkmodeSupported");
|
||||
store.dispatch("user/initUserFromCookie");
|
||||
|
||||
new Vue({
|
||||
el: "#app",
|
||||
router,
|
||||
store,
|
||||
components: { App },
|
||||
template: "<App />"
|
||||
});
|
||||
16
src/main.ts
Normal file
16
src/main.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { createApp } from "vue";
|
||||
import router from "./routes";
|
||||
import store from "./store";
|
||||
|
||||
import Toast from "./plugins/Toast";
|
||||
const App = require("./App.vue").default;
|
||||
|
||||
store.dispatch("darkmodeModule/findAndSetDarkmodeSupported");
|
||||
store.dispatch("user/initUserFromCookie");
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
app.use(router);
|
||||
app.use(store);
|
||||
app.use(Toast);
|
||||
app.mount("#entry");
|
||||
@@ -1,189 +1,172 @@
|
||||
<template>
|
||||
<transition name="slide">
|
||||
<div
|
||||
v-if="show"
|
||||
@click="clicked"
|
||||
class="toast"
|
||||
:class="type">
|
||||
|
||||
<div class="toast--content">
|
||||
<div class=" toast--icon">
|
||||
<i v-if="image"><img class="toast--icon-image" :src="image" /></i>
|
||||
</div>
|
||||
<div class="toast--text" v-if="description">
|
||||
<span class="toast--text__title">{{title}}</span>
|
||||
<br /><span class="toast--text__description" v-html="description"></span>
|
||||
</div>
|
||||
<div class="toast--text" v-else>
|
||||
<span class="toast--text__title-large">{{title}}</span>
|
||||
</div>
|
||||
<div class="toast--dismiss" @click="dismiss">
|
||||
<i class="fas fa-times"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="show" @click="clicked" class="toast" :class="type">
|
||||
<div class="toast--content">
|
||||
<div class="toast--icon">
|
||||
<i v-if="image"><img class="toast--icon-image" :src="image" /></i>
|
||||
</div>
|
||||
<div class="toast--text" v-if="description">
|
||||
<span class="toast--text__title">{{ title }}</span>
|
||||
<br /><span
|
||||
class="toast--text__description"
|
||||
v-html="description"
|
||||
></span>
|
||||
</div>
|
||||
<div class="toast--text" v-else>
|
||||
<span class="toast--text__title-large">{{ title }}</span>
|
||||
</div>
|
||||
<div class="toast--dismiss" @click="dismiss">
|
||||
<i class="fas fa-times"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
type: this.$root.type || 'info',
|
||||
title: this.$root.title || undefined,
|
||||
description: this.$root.description || undefined,
|
||||
image: this.$root.image || undefined,
|
||||
link: this.$root.link || undefined,
|
||||
timeout: this.$root.timeout || 2000,
|
||||
show: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// Here we set show when mounted in-order to get the transition animation to be displayed correctly
|
||||
this.show = true;
|
||||
<script setup lang="ts">
|
||||
import { ref, defineProps, onMounted } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import type { Ref } from "vue";
|
||||
|
||||
interface Props {
|
||||
type: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
image?: string;
|
||||
link?: string;
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
const {
|
||||
type = "info",
|
||||
title,
|
||||
description,
|
||||
image,
|
||||
link,
|
||||
timeout = 2000
|
||||
} = defineProps<Props>();
|
||||
const router = useRouter();
|
||||
|
||||
const show: Ref<boolean> = ref(false);
|
||||
|
||||
onMounted(() => {
|
||||
show.value = true;
|
||||
|
||||
setTimeout(() => {
|
||||
console.log('Your time is up 👋')
|
||||
this.show = false;
|
||||
}, this.timeout);
|
||||
},
|
||||
methods: {
|
||||
clicked() {
|
||||
if (this.link) {
|
||||
let resolved = this.$root.router.resolve(this.link);
|
||||
|
||||
if (resolved && resolved.route.name !== '404') {
|
||||
this.$root.router.push(this.link);
|
||||
} else {
|
||||
console.error('Found a link but it resolved to 404. Link:', this.link)
|
||||
}
|
||||
} else {
|
||||
this.show = false;
|
||||
}
|
||||
},
|
||||
dismiss() {
|
||||
this.show = false;
|
||||
},
|
||||
move(e) {
|
||||
console.log('moving', e)
|
||||
let target = e.target;
|
||||
console.log("Notification time is up 👋");
|
||||
show.value = false;
|
||||
}, timeout);
|
||||
});
|
||||
|
||||
console.log('target', target)
|
||||
function clicked() {
|
||||
show.value = false;
|
||||
|
||||
var div = document.getElementById('target');
|
||||
div.style.position = 'absolute';
|
||||
div.style.top = e.clientY + 'px';
|
||||
div.style.left = e.clientX + 'px';
|
||||
},
|
||||
if (link) {
|
||||
router.push(link);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// @import '@/scss/variables.scss';
|
||||
|
||||
|
||||
.slide-enter-active {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.slide-enter, .slide-leave-to {
|
||||
transform: translateY(100vh);
|
||||
opacity: 0;
|
||||
}
|
||||
.slide-leave-active {
|
||||
transition: all 2s ease;
|
||||
}
|
||||
|
||||
.toast--icon-image {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
max-height: 45px;
|
||||
max-width: 45px;
|
||||
}
|
||||
|
||||
.toast {
|
||||
position: fixed;
|
||||
bottom: 0.5rem;
|
||||
cursor: pointer;
|
||||
z-index: 100;
|
||||
|
||||
background-color: white;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.17), 0 2px 4px 0 rgba(0,0,0,0.08);
|
||||
padding: 0.5rem;
|
||||
margin: 1rem 2rem 1rem 0.71rem;
|
||||
// max-width: calc(100% - 3rem);
|
||||
min-width: 320px;
|
||||
|
||||
|
||||
// If small screen we have a min-width that is related to the screen size.
|
||||
// else large screens we want a max-width that only uses the space in bottom right
|
||||
|
||||
right: 0;
|
||||
line-height: 22.5px;
|
||||
|
||||
&--content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
// @import '@/scss/variables.scss';
|
||||
|
||||
.slide-enter-active {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.slide-enter,
|
||||
.slide-leave-to {
|
||||
transform: translateY(100vh);
|
||||
opacity: 0;
|
||||
}
|
||||
.slide-leave-active {
|
||||
transition: all 2s ease;
|
||||
}
|
||||
|
||||
&--icon {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
.toast--icon-image {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
max-height: 45px;
|
||||
max-width: 45px;
|
||||
}
|
||||
|
||||
&--text {
|
||||
margin-left: 0.5rem;
|
||||
// color: $bt-brown;
|
||||
color: black;
|
||||
word-wrap: break-word;
|
||||
.toast {
|
||||
position: fixed;
|
||||
bottom: 0.5rem;
|
||||
cursor: pointer;
|
||||
z-index: 100;
|
||||
|
||||
&__title {
|
||||
text-transform: capitalize;
|
||||
font-weight: 400;
|
||||
background-color: white;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.17), 0 2px 4px 0 rgba(0, 0, 0, 0.08);
|
||||
padding: 0.5rem;
|
||||
margin: 1rem 2rem 1rem 0.71rem;
|
||||
// max-width: calc(100% - 3rem);
|
||||
min-width: 320px;
|
||||
|
||||
&-large {
|
||||
font-size: 2rem;
|
||||
// If small screen we have a min-width that is related to the screen size.
|
||||
// else large screens we want a max-width that only uses the space in bottom right
|
||||
|
||||
right: 0;
|
||||
line-height: 22.5px;
|
||||
|
||||
&--content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&--icon {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&--text {
|
||||
margin-left: 0.5rem;
|
||||
// color: $bt-brown;
|
||||
color: black;
|
||||
word-wrap: break-word;
|
||||
|
||||
&__title {
|
||||
text-transform: capitalize;
|
||||
font-weight: 400;
|
||||
|
||||
&-large {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
&__description {
|
||||
font-weight: 300;
|
||||
}
|
||||
}
|
||||
|
||||
&__description {
|
||||
font-weight: 300;
|
||||
&--dismiss {
|
||||
align-self: flex-end;
|
||||
|
||||
img {
|
||||
width: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.success {
|
||||
border-left: 6px solid #38c172;
|
||||
}
|
||||
|
||||
&.info {
|
||||
border-left: 6px solid #ffd300;
|
||||
}
|
||||
|
||||
&.warning {
|
||||
border-left: 6px solid #f6993f;
|
||||
}
|
||||
|
||||
&.error {
|
||||
border-left: 6px solid #e3342f;
|
||||
}
|
||||
|
||||
&.simple {
|
||||
border-left: unset;
|
||||
}
|
||||
}
|
||||
|
||||
&--dismiss {
|
||||
align-self: flex-end;
|
||||
|
||||
img {
|
||||
width: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&.success {
|
||||
border-left: 6px solid #38c172;
|
||||
}
|
||||
|
||||
&.info {
|
||||
border-left: 6px solid #FFD300;
|
||||
}
|
||||
|
||||
&.warning {
|
||||
border-left: 6px solid #f6993f;
|
||||
}
|
||||
|
||||
&.error {
|
||||
border-left: 6px solid #e3342f;
|
||||
}
|
||||
|
||||
&.simple {
|
||||
border-left: unset;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
import Vue from 'vue'
|
||||
import ToastComponent from './ToastComponent.vue'
|
||||
|
||||
const optionsDefaults = {
|
||||
data: {
|
||||
type: 'info',
|
||||
show: true,
|
||||
timeout: 3000,
|
||||
|
||||
onCreate(created = null) {
|
||||
},
|
||||
onEdit(editted = null) {
|
||||
},
|
||||
onRemove(removed = null) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toast(options, router) {
|
||||
// merge the default options with the passed options.
|
||||
const root = new Vue({
|
||||
data: {
|
||||
...optionsDefaults.data,
|
||||
...options,
|
||||
router
|
||||
},
|
||||
render: createElement => createElement(ToastComponent)
|
||||
})
|
||||
|
||||
root.$mount(document.body.appendChild(document.createElement('div')))
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
install(vue, opts) {
|
||||
console.log('installing toast plugin!')
|
||||
console.log('plugin options', opts)
|
||||
|
||||
Vue.prototype.$notifications = {
|
||||
info(options) {
|
||||
toast({ type: 'info', ...options })
|
||||
},
|
||||
success(options) {
|
||||
toast({ type: 'success', ...options })
|
||||
},
|
||||
warning(options) {
|
||||
toast({ type: 'warning', ...options })
|
||||
},
|
||||
error(options) {
|
||||
toast({ type: 'error', ...options })
|
||||
},
|
||||
simple(options) {
|
||||
toast({ type: 'simple', ...options})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
68
src/plugins/Toast/index.ts
Normal file
68
src/plugins/Toast/index.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { createApp } from "vue";
|
||||
import router from "../../routes";
|
||||
import ToastComponent from "./ToastComponent.vue";
|
||||
|
||||
const optionsDefaults = {
|
||||
data: {
|
||||
type: "info",
|
||||
show: true,
|
||||
timeout: 3000,
|
||||
|
||||
onCreate(created = null) {},
|
||||
onEdit(editted = null) {},
|
||||
onRemove(removed = null) {}
|
||||
}
|
||||
};
|
||||
|
||||
function toast(options) {
|
||||
// merge the default options with the passed options.
|
||||
const toastComponent = createApp(ToastComponent, {
|
||||
...optionsDefaults.data,
|
||||
...options
|
||||
});
|
||||
|
||||
toastComponent.use(router);
|
||||
|
||||
console.log("toastComponent:", toastComponent);
|
||||
toastComponent.mount(
|
||||
document.body.appendChild(document.createElement("div"))
|
||||
);
|
||||
}
|
||||
|
||||
export default {
|
||||
install(app, options) {
|
||||
console.log("installing toast plugin!");
|
||||
console.log("plugin options", options);
|
||||
|
||||
function info(options) {
|
||||
toast({ type: "info", ...options });
|
||||
}
|
||||
|
||||
function success(options) {
|
||||
toast({ type: "success", ...options });
|
||||
}
|
||||
|
||||
function warning(options) {
|
||||
toast({ type: "warning", ...options });
|
||||
}
|
||||
|
||||
function error(options) {
|
||||
toast({ type: "error", ...options });
|
||||
}
|
||||
|
||||
function simple(options) {
|
||||
toast({ type: "simple", ...options });
|
||||
}
|
||||
|
||||
const notifications = { info, success, warning, error, simple };
|
||||
app.config.globalProperties.$notifications = notifications;
|
||||
|
||||
app.provide("notifications", notifications);
|
||||
}
|
||||
};
|
||||
|
||||
declare module "@vue/runtime-core" {
|
||||
interface ComponentCustomProperties {
|
||||
$notifications: (info: object) => void;
|
||||
}
|
||||
}
|
||||
105
src/routes.ts
105
src/routes.ts
@@ -1,5 +1,11 @@
|
||||
import Vue from "vue";
|
||||
import VueRouter, { NavigationGuardNext, Route, RouteConfig } from "vue-router";
|
||||
import { defineAsyncComponent } from "vue";
|
||||
import {
|
||||
createRouter,
|
||||
createWebHistory,
|
||||
RouteRecordRaw,
|
||||
NavigationGuardNext,
|
||||
RouteLocationNormalized
|
||||
} from "vue-router";
|
||||
import store from "./store";
|
||||
|
||||
declare global {
|
||||
@@ -8,57 +14,61 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
let routes = [
|
||||
let routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
name: "home",
|
||||
path: "/",
|
||||
component: resolve => resolve("./pages/Home.vue")
|
||||
component: () => import("./pages/Home.vue")
|
||||
},
|
||||
{
|
||||
name: "activity",
|
||||
path: "/activity",
|
||||
meta: { requiresAuth: true },
|
||||
component: resolve => resolve("./pages/ActivityPage.vue")
|
||||
component: () => import("./pages/ActivityPage.vue")
|
||||
},
|
||||
{
|
||||
name: "profile",
|
||||
path: "/profile",
|
||||
meta: { requiresAuth: true },
|
||||
component: resolve => resolve("./pages/ProfilePage.vue")
|
||||
component: () => import("./pages/ProfilePage.vue")
|
||||
},
|
||||
{
|
||||
name: "list",
|
||||
name: "requests-list",
|
||||
path: "/list/requests",
|
||||
component: resolve => resolve("./pages/RequestPage.vue")
|
||||
component: () => import("./pages/RequestPage.vue")
|
||||
},
|
||||
{
|
||||
name: "list",
|
||||
path: "/list/:name",
|
||||
component: resolve => resolve("./pages/ListPage.vue")
|
||||
component: () => import("./pages/ListPage.vue")
|
||||
},
|
||||
{
|
||||
name: "search",
|
||||
path: "/search",
|
||||
component: resolve => resolve("./pages/SearchPage.vue")
|
||||
component: () => import("./pages/SearchPage.vue")
|
||||
},
|
||||
{
|
||||
name: "register",
|
||||
path: "/register",
|
||||
component: resolve => resolve("./pages/RegisterPage.vue")
|
||||
component: () => import("./pages/RegisterPage.vue")
|
||||
},
|
||||
{
|
||||
name: "settings",
|
||||
path: "/settings",
|
||||
meta: { requiresAuth: true },
|
||||
component: resolve => resolve("./pages/SettingsPage.vue")
|
||||
component: () => import("./pages/SettingsPage.vue")
|
||||
},
|
||||
{
|
||||
name: "signin",
|
||||
path: "/signin",
|
||||
alias: "/login",
|
||||
component: resolve => resolve("./pages/SigninPage.vue")
|
||||
component: () => import("./pages/SigninPage.vue")
|
||||
},
|
||||
{
|
||||
name: "torrents",
|
||||
path: "/torrents",
|
||||
meta: { requiresAuth: true },
|
||||
component: () => import("./pages/TorrentsPage.vue")
|
||||
},
|
||||
// {
|
||||
// name: 'user-requests',
|
||||
@@ -70,21 +80,21 @@ let routes = [
|
||||
{
|
||||
name: "404",
|
||||
path: "/404",
|
||||
component: resolve => resolve("./pages/404.vue")
|
||||
},
|
||||
{
|
||||
path: "*",
|
||||
redirect: "/"
|
||||
},
|
||||
{
|
||||
path: "/request",
|
||||
redirect: "/"
|
||||
component: () => import("./pages/404.vue")
|
||||
}
|
||||
// {
|
||||
// path: "*",
|
||||
// redirect: "/"
|
||||
// },
|
||||
// {
|
||||
// path: "/request",
|
||||
// redirect: "/"
|
||||
// }
|
||||
];
|
||||
|
||||
const router = new VueRouter({
|
||||
mode: "history",
|
||||
base: "/",
|
||||
const router = createRouter({
|
||||
history: createWebHistory("/"),
|
||||
// base: "/",
|
||||
routes,
|
||||
linkActiveClass: "is-active"
|
||||
});
|
||||
@@ -93,35 +103,24 @@ const loggedIn = () => store.getters["user/loggedIn"];
|
||||
const popupIsOpen = () => store.getters["popup/isOpen"];
|
||||
const hamburgerIsOpen = () => store.getters["hamburger/isOpen"];
|
||||
|
||||
window.preventPushState = false;
|
||||
window.onpopstate = () => (window.preventPushState = true);
|
||||
router.beforeEach(
|
||||
(to: RouteLocationNormalized, from: RouteLocationNormalized, next: any) => {
|
||||
store.dispatch("documentTitle/updateTitle", to.name);
|
||||
store.dispatch("popup/resetStateFromUrlQuery", to.query);
|
||||
|
||||
router.beforeEach((to: Route, from: Route, next: NavigationGuardNext<Vue>) => {
|
||||
store.dispatch("documentTitle/updateTitle", to.name);
|
||||
const { movie, show, person } = to.query;
|
||||
// Every route change we close hamburger if open
|
||||
if (hamburgerIsOpen()) store.dispatch("hamburger/close");
|
||||
|
||||
if (movie) store.dispatch("popup/open", { id: movie, type: "movie" });
|
||||
else if (show) store.dispatch("popup/open", { id: show, type: "show" });
|
||||
else if (person) store.dispatch("popup/open", { id: person, type: "person" });
|
||||
else store.dispatch("popup/close");
|
||||
|
||||
if (hamburgerIsOpen()) store.dispatch("hamburger/close");
|
||||
|
||||
// Toggle mobile nav
|
||||
if (document.querySelector(".nav__hamburger--active")) {
|
||||
document
|
||||
.querySelector(".nav__hamburger")
|
||||
.classList.remove("nav__hamburger--active");
|
||||
document.querySelector(".nav__list").classList.remove("nav__list--active");
|
||||
}
|
||||
|
||||
if (to.matched.some(record => record.meta.requiresAuth)) {
|
||||
if (!loggedIn) {
|
||||
next({ path: "/signin" });
|
||||
// If pages has meta 'requiresAuth' and user not logged in
|
||||
// send user to signin page.
|
||||
if (to.matched.some(record => record.meta.requiresAuth)) {
|
||||
if (!loggedIn) {
|
||||
next({ path: "/signin" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
next();
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import Vue from "vue";
|
||||
import Vuex from "vuex";
|
||||
import { createStore } from "vuex";
|
||||
|
||||
import darkmodeModule from "./modules/darkmodeModule";
|
||||
import documentTitle from "./modules/documentTitle";
|
||||
@@ -8,9 +7,7 @@ import user from "./modules/user";
|
||||
import popup from "./modules/popup";
|
||||
import hamburger from "./modules/hamburger";
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
const store = new Vuex.Store({
|
||||
const store = createStore({
|
||||
modules: {
|
||||
darkmodeModule,
|
||||
documentTitle,
|
||||
|
||||
66
src/utils.ts
66
src/utils.ts
@@ -1,8 +1,8 @@
|
||||
export const sortableSize = (string: string) => {
|
||||
export const sortableSize = (string: string): number => {
|
||||
const UNITS = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||
const [numStr, unit] = string.split(" ");
|
||||
|
||||
if (UNITS.indexOf(unit) === -1) return string;
|
||||
if (UNITS.indexOf(unit) === -1) return null;
|
||||
|
||||
const exponent = UNITS.indexOf(unit) * 3;
|
||||
return Number(numStr) * Math.pow(10, exponent);
|
||||
@@ -36,3 +36,65 @@ export const buildImageProxyUrl = (
|
||||
|
||||
return `${proxyHost}${proxySizeOptions}${assetUrl}`;
|
||||
};
|
||||
|
||||
export function focusFirstFormInput(formElement: HTMLFormElement): void {
|
||||
if (!formElement) return;
|
||||
|
||||
const firstInput = formElement?.getElementsByTagName("input")[0];
|
||||
if (!firstInput) return;
|
||||
|
||||
firstInput.focus();
|
||||
}
|
||||
|
||||
export function focusOnNextElement(elementEvent: KeyboardEvent): void {
|
||||
const { target } = elementEvent;
|
||||
console.log("target:", target);
|
||||
if (!target) return;
|
||||
|
||||
const form = document.getElementsByTagName("form")[0];
|
||||
console.log("form:", form);
|
||||
if (!form) return;
|
||||
|
||||
const inputElements = form.getElementsByTagName("input");
|
||||
console.log("inputElements:", inputElements);
|
||||
const targetIndex = Array.from(inputElements).findIndex(
|
||||
element => element === target
|
||||
);
|
||||
console.log("targetIndex:", targetIndex);
|
||||
if (targetIndex < inputElements.length) {
|
||||
inputElements[targetIndex + 1].focus();
|
||||
}
|
||||
}
|
||||
|
||||
export function humanMinutes(minutes) {
|
||||
if (minutes instanceof Array) {
|
||||
minutes = minutes[0];
|
||||
}
|
||||
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const minutesLeft = minutes - hours * 60;
|
||||
|
||||
if (minutesLeft == 0) {
|
||||
return hours > 1 ? `${hours} hours` : `${hours} hour`;
|
||||
} else if (hours == 0) {
|
||||
return `${minutesLeft} min`;
|
||||
}
|
||||
|
||||
return `${hours}h ${minutesLeft}m`;
|
||||
}
|
||||
|
||||
export function getValueFromUrlQuery(queryParameter: string): string | null {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
return params.get(queryParameter) || null;
|
||||
}
|
||||
|
||||
export function setUrlQueryParameter(parameter: string, value: string): void {
|
||||
const params = new URLSearchParams();
|
||||
params.append(parameter, value);
|
||||
|
||||
const url = `${window.location.protocol}//${window.location.hostname}${
|
||||
window.location.port ? `:${window.location.port}` : ""
|
||||
}${window.location.pathname}${params.toString().length ? `?${params}` : ""}`;
|
||||
|
||||
window.history.pushState({}, "search", url);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ const TerserPlugin = require("terser-webpack-plugin");
|
||||
|
||||
const sourcePath = path.resolve(__dirname, "src");
|
||||
const indexFile = path.join(sourcePath, "index.html");
|
||||
const javascriptEntry = path.join(sourcePath, "main.js");
|
||||
const javascriptEntry = path.join(sourcePath, "main.ts");
|
||||
const publicPath = path.resolve(__dirname, "public");
|
||||
const isProd = process.env.NODE_ENV === "production";
|
||||
|
||||
@@ -36,9 +36,12 @@ module.exports = {
|
||||
use: ["vue-loader"]
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
test: /\.ts$/,
|
||||
loader: "ts-loader",
|
||||
exclude: /node_modules/
|
||||
exclude: /node_modules/,
|
||||
options: {
|
||||
appendTsSuffixTo: [/\.vue$/]
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
@@ -69,7 +72,7 @@ module.exports = {
|
||||
resolve: {
|
||||
extensions: [".js", ".ts", ".vue", ".json", ".scss"],
|
||||
alias: {
|
||||
vue$: "vue/dist/vue.common.js",
|
||||
vue: "@vue/runtime-dom",
|
||||
"@": path.resolve(__dirname, "src"),
|
||||
src: path.resolve(__dirname, "src"),
|
||||
assets: `${publicPath}/assets`,
|
||||
|
||||
Reference in New Issue
Block a user