mirror of https://github.com/ErsatzTV/ErsatzTV.git
Browse Source
* add vue ui
* add channels mock api
* Initial Vue framework with Vuetify UI (#688)
* fix hls direct streaming mode (#682)
* duration analysis on files with missing duration metadata (#683)
* first pass
* analyze zero-duration files
* add readme note for WIP
* add vuetify and basic sidebar layout - responsive
* add vue-router and initial home page
* setup composition-api for vue2
* install pinia ie... vuex4
* mixing for automatic page title
* add logo files
* tweak theme colors
* install store
* use store for menu toggle
* replicate old menu
* implement menu and children menus
* rename state to application state
* update vue files and add version to sidebar
* lock logo and make expandable list remove minified menu
* remove todo, will add to PR
* top bar links and attempt at snackbar with state
* fix snackbar
* add search basic component
* fix search bar placement
* remove un-used footer
* Revert "Merge branch 'jasongdove:main' into intitial-vuetify-ui"
This reverts commit 43016d502b
.
Co-authored-by: Jason Dove <jason@jasondove.me>
* Add ESLint and Prettier for VueJS (#691)
* add prettier to config
* run npm run lint over project to clean up files
* replace hr tag with v-dividers
* add vue-lint github action
* fix the dodo in me
* Hu
* Fix path
* convert to multi-run step
* forgot the name
* add vue-lint github action
fix the dodo in me
Hu
Fix path
convert to multi-run step
forgot the name
* Fix new line at end of file
* WIP
* dockerfile consistency
* use npm ci and node v14
* force prettier indenting and end of line (#695)
* disable filename hashing
* don't build tests in docker
* update dependencies
* fix running both uis
Co-authored-by: James Mackay <info@notexpectedyet.com>
pull/700/head
36 changed files with 21600 additions and 22 deletions
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
name: Lint VueJS Files on PR Request |
||||
on: |
||||
pull_request: |
||||
jobs: |
||||
vue-lint: |
||||
runs-on: ubuntu-latest |
||||
steps: |
||||
# Checkout the current repo |
||||
- name: Checkout current repository |
||||
uses: actions/checkout@v2 |
||||
# Setup NodeJS version 14 |
||||
- name: Setup NodeJS V14.x.x |
||||
uses: actions/setup-node@v2 |
||||
with: |
||||
node-version: '14' |
||||
# CD into the current client directory and lint and build the client |
||||
- name: Lint and Build the client |
||||
run: | |
||||
cd ./ErsatzTV/client-app/ |
||||
npm ci --no-optional |
||||
npm run lint |
||||
npm run build --if-present |
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
.DS_Store |
||||
node_modules |
||||
/dist |
||||
|
||||
|
||||
# local env files |
||||
.env.local |
||||
.env.*.local |
||||
|
||||
# Log files |
||||
npm-debug.log* |
||||
yarn-debug.log* |
||||
yarn-error.log* |
||||
pnpm-debug.log* |
||||
|
||||
# Editor directories and files |
||||
.idea |
||||
.vscode |
||||
*.suo |
||||
*.ntvs* |
||||
*.njsproj |
||||
*.sln |
||||
*.sw? |
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
# ErsatzTV Vuetify UI - WIP |
||||
|
||||
## Project setup |
||||
``` |
||||
npm install |
||||
``` |
||||
|
||||
### Compiles and hot-reloads for development |
||||
``` |
||||
npm run serve |
||||
``` |
||||
|
||||
### Compiles and minifies for production |
||||
``` |
||||
npm run build |
||||
``` |
||||
|
||||
### Lints and fixes files |
||||
``` |
||||
npm run lint |
||||
``` |
||||
|
||||
### Customize configuration |
||||
See [Configuration Reference](https://cli.vuejs.org/config/). |
||||
|
@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
|
||||
module.exports = { |
||||
presets: ["@vue/cli-plugin-babel/preset"], |
||||
}; |
@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
|
||||
{ |
||||
"compilerOptions": { |
||||
"target": "es5", |
||||
"module": "esnext", |
||||
"baseUrl": "./", |
||||
"moduleResolution": "node", |
||||
"paths": { |
||||
"@/*": [ |
||||
"src/*" |
||||
] |
||||
}, |
||||
"lib": [ |
||||
"esnext", |
||||
"dom", |
||||
"dom.iterable", |
||||
"scripthost" |
||||
] |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,63 @@
@@ -0,0 +1,63 @@
|
||||
{ |
||||
"name": "client-app", |
||||
"version": "0.1.0", |
||||
"private": true, |
||||
"scripts": { |
||||
"serve": "vue-cli-service serve", |
||||
"build": "vue-cli-service build", |
||||
"lint": "vue-cli-service lint" |
||||
}, |
||||
"dependencies": { |
||||
"@mdi/font": "5.9.55", |
||||
"core-js": "^3.8.3", |
||||
"pinia": "^2.0.11", |
||||
"roboto-fontface": "*", |
||||
"vue": "^2.6.14", |
||||
"vue-router": "^3.2.0", |
||||
"vuetify": "^2.6.0" |
||||
}, |
||||
"devDependencies": { |
||||
"@babel/core": "^7.12.16", |
||||
"@babel/eslint-parser": "^7.12.16", |
||||
"@vue/cli-plugin-babel": "~5.0.0", |
||||
"@vue/cli-plugin-eslint": "~5.0.0", |
||||
"@vue/cli-service": "~5.0.0", |
||||
"@vue/composition-api": "^1.4.9", |
||||
"eslint": "^7.32.0", |
||||
"eslint-config-prettier": "^8.5.0", |
||||
"eslint-plugin-prettier": "^4.0.0", |
||||
"eslint-plugin-vue": "^8.0.3", |
||||
"sass": "~1.32.0", |
||||
"sass-loader": "^10.0.0", |
||||
"vue-cli-plugin-vuetify": "~2.4.7", |
||||
"vue-template-compiler": "^2.6.14", |
||||
"vuetify-loader": "^1.7.0" |
||||
}, |
||||
"eslintConfig": { |
||||
"root": true, |
||||
"env": { |
||||
"node": true |
||||
}, |
||||
"extends": [ |
||||
"plugin:vue/essential", |
||||
"plugin:prettier/recommended", |
||||
"eslint:recommended" |
||||
], |
||||
"parserOptions": { |
||||
"parser": "@babel/eslint-parser" |
||||
}, |
||||
"rules": { |
||||
"prettier/prettier": [ |
||||
"error", { |
||||
"endOfLine": "auto", |
||||
"tabWidth": 4 |
||||
} |
||||
] |
||||
} |
||||
}, |
||||
"browserslist": [ |
||||
"> 1%", |
||||
"last 2 versions", |
||||
"not dead" |
||||
] |
||||
} |
After Width: | Height: | Size: 15 KiB |
@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> |
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0"> |
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> |
||||
<title><%= htmlWebpackPlugin.options.title %></title> |
||||
</head> |
||||
<body> |
||||
<noscript> |
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> |
||||
</noscript> |
||||
<div id="app"></div> |
||||
<!-- built files will be auto injected --> |
||||
</body> |
||||
</html> |
@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
|
||||
<template> |
||||
<v-app> |
||||
<Toolbar /> |
||||
<!-- Sizes your content based upon application components --> |
||||
<v-main> |
||||
<!-- Provides the application the proper gutter --> |
||||
<v-container fluid> |
||||
<!-- If using vue-router --> |
||||
<router-view></router-view> |
||||
</v-container> |
||||
<SnackBar /> |
||||
</v-main> |
||||
</v-app> |
||||
</template> |
||||
|
||||
<script> |
||||
import Vue from "vue"; |
||||
import Toolbar from "@/components/Navigation/ToolBar.vue"; |
||||
import SnackBar from "@/components/PopUps/SnackBar"; |
||||
export default Vue.extend({ |
||||
name: "App", |
||||
components: { Toolbar, SnackBar }, |
||||
data: () => ({}), |
||||
}); |
||||
</script> |
||||
<style> |
||||
main { |
||||
background-color: black; |
||||
background-attachment: fixed; |
||||
background-repeat: no-repeat; |
||||
-webkit-background-size: cover; |
||||
-moz-background-size: cover; |
||||
-o-background-size: cover; |
||||
background-size: cover; |
||||
background-position: center; |
||||
} |
||||
</style> |
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 4.7 KiB |
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
<template> |
||||
<span> |
||||
<v-img |
||||
v-if="!isNavigationMini" |
||||
src="@/assets/images/ersatztv.png" |
||||
class="ma-3" |
||||
></v-img> |
||||
<v-img |
||||
v-if="isNavigationMini" |
||||
src="@/assets/images/ersatztv-500.png" |
||||
class="ma-1" |
||||
></v-img> |
||||
<v-divider inset></v-divider> |
||||
</span> |
||||
</template> |
||||
|
||||
<script> |
||||
import { mapState } from "pinia"; |
||||
import { applicationState } from "@/stores/applicationState"; |
||||
|
||||
export default { |
||||
name: "SideBarLogo", |
||||
computed: { |
||||
...mapState(applicationState, ["isNavigationMini"]), |
||||
}, |
||||
}; |
||||
</script> |
@ -0,0 +1,43 @@
@@ -0,0 +1,43 @@
|
||||
<template> |
||||
<v-list nav dense> |
||||
<span v-for="(nav, i) in navigation" :key="i"> |
||||
<SideBarMenuItem |
||||
v-if="!nav.children" |
||||
:name="nav.name" |
||||
:path="nav.path" |
||||
:icon="nav.meta.icon" |
||||
:disabled="nav.meta.disabled" |
||||
/> |
||||
<SideBarMenuItemExpandable |
||||
v-else |
||||
@click.native="disableMiniNavigation()" |
||||
:name="nav.name" |
||||
:icon="nav.meta.icon" |
||||
:disabled="nav.meta.disabled" |
||||
:children="nav.children" |
||||
/> |
||||
</span> |
||||
</v-list> |
||||
</template> |
||||
|
||||
<script> |
||||
import SideBarMenuItem from "./SideBarMenuItem"; |
||||
import SideBarMenuItemExpandable from "./SideBarMenuItemExpandable"; |
||||
import { mapState } from "pinia"; |
||||
import { applicationState } from "@/stores/applicationState"; |
||||
|
||||
export default { |
||||
name: "NavSidebar", |
||||
components: { SideBarMenuItem, SideBarMenuItemExpandable }, |
||||
data: () => ({ |
||||
navigation: null, |
||||
}), |
||||
computed: { |
||||
...mapState(applicationState, ["disableMiniNavigation"]), |
||||
}, |
||||
beforeMount: function () { |
||||
//Pull in navigation from routes and load into DOM |
||||
this.navigation = this.$router.options.routes; |
||||
}, |
||||
}; |
||||
</script> |
@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
|
||||
<template> |
||||
<v-list-item-group color="primary"> |
||||
<v-list-item :to="path" :disabled="disabled"> |
||||
<v-list-item-icon> |
||||
<v-icon v-text="icon" :disabled="disabled" /> |
||||
</v-list-item-icon> |
||||
|
||||
<v-list-item-content> |
||||
<v-list-item-title v-text="name" /> |
||||
</v-list-item-content> |
||||
</v-list-item> |
||||
</v-list-item-group> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: "SideBarMenuItem", |
||||
props: { |
||||
name: { |
||||
type: String, |
||||
required: true, |
||||
}, |
||||
path: { |
||||
type: String, |
||||
required: true, |
||||
}, |
||||
icon: { |
||||
type: String, |
||||
required: true, |
||||
}, |
||||
disabled: { |
||||
type: Boolean, |
||||
required: true, |
||||
}, |
||||
}, |
||||
}; |
||||
</script> |
@ -0,0 +1,69 @@
@@ -0,0 +1,69 @@
|
||||
<template> |
||||
<v-list-group |
||||
color="primary" |
||||
v-model="menuItemOpened" |
||||
:disabled="disabled" |
||||
:prepend-icon="icon" |
||||
no-action |
||||
> |
||||
<template v-slot:activator> |
||||
<v-list-item-content> |
||||
<v-list-item-title v-text="name"></v-list-item-title> |
||||
</v-list-item-content> |
||||
</template> |
||||
|
||||
<v-list-item |
||||
v-for="child in children" |
||||
:key="child.name" |
||||
:to="child.path" |
||||
:disabled="child.meta.disabled" |
||||
> |
||||
<v-list-item-icon> |
||||
<v-icon |
||||
v-text="child.meta.icon" |
||||
:disabled="child.meta.disabled" |
||||
/> |
||||
</v-list-item-icon> |
||||
<v-list-item-content> |
||||
<v-list-item-title v-text="child.name"></v-list-item-title> |
||||
</v-list-item-content> |
||||
</v-list-item> |
||||
</v-list-group> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: "SideBarMenuItemExpandable", |
||||
props: { |
||||
name: { |
||||
type: String, |
||||
required: true, |
||||
}, |
||||
icon: { |
||||
type: String, |
||||
required: true, |
||||
}, |
||||
disabled: { |
||||
type: Boolean, |
||||
required: true, |
||||
}, |
||||
children: { |
||||
type: Array, |
||||
required: true, |
||||
}, |
||||
}, |
||||
data: () => ({ |
||||
opened: false, |
||||
}), |
||||
computed: { |
||||
menuItemOpened: { |
||||
get: function () { |
||||
return this.opened; |
||||
}, |
||||
set: function () { |
||||
return !this.opened; |
||||
}, |
||||
}, |
||||
}, |
||||
}; |
||||
</script> |
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
<template> |
||||
<span v-if="!isNavigationMini" class="text-center"> |
||||
<v-divider inset></v-divider> |
||||
<h4 class="pt-2">ErsatzTV Version</h4> |
||||
|
||||
<p>{{ currentServerVersion }}</p> |
||||
</span> |
||||
</template> |
||||
|
||||
<script> |
||||
import { mapState } from "pinia"; |
||||
import { applicationState } from "@/stores/applicationState"; |
||||
|
||||
export default { |
||||
name: "SideBarVersion", |
||||
computed: { |
||||
...mapState(applicationState, [ |
||||
"currentServerVersion", |
||||
"isNavigationMini", |
||||
]), |
||||
}, |
||||
}; |
||||
</script> |
||||
|
||||
<style scoped></style> |
@ -0,0 +1,49 @@
@@ -0,0 +1,49 @@
|
||||
<template> |
||||
<nav> |
||||
<v-app-bar flat app dense absolute> |
||||
<v-app-bar-nav-icon @click.stop="toggleMiniNavigation()" /> |
||||
|
||||
<ToolBarSearch /> |
||||
<v-spacer /> |
||||
|
||||
<ToolBarLinks /> |
||||
</v-app-bar> |
||||
<v-navigation-drawer app :mini-variant="isNavigationMini" permanent> |
||||
<template v-slot:prepend> |
||||
<SideBarLogo /> |
||||
</template> |
||||
|
||||
<SideBarMenu /> |
||||
<template v-slot:append> |
||||
<SideBarVersion /> |
||||
</template> |
||||
</v-navigation-drawer> |
||||
</nav> |
||||
</template> |
||||
|
||||
<script> |
||||
import SideBarLogo from "./SideBarLogo.vue"; |
||||
import SideBarMenu from "./SideBarMenu.vue"; |
||||
import SideBarVersion from "./SideBarVersion"; |
||||
import ToolBarLinks from "./ToolBarLinks"; |
||||
import ToolBarSearch from "./ToolBarSearch"; |
||||
import { mapState } from "pinia"; |
||||
import { applicationState } from "@/stores/applicationState"; |
||||
|
||||
export default { |
||||
name: "NavToolbar", |
||||
components: { |
||||
SideBarMenu, |
||||
SideBarLogo, |
||||
SideBarVersion, |
||||
ToolBarLinks, |
||||
ToolBarSearch, |
||||
}, |
||||
computed: { |
||||
...mapState(applicationState, [ |
||||
"isNavigationMini", |
||||
"toggleMiniNavigation", |
||||
]), |
||||
}, |
||||
}; |
||||
</script> |
@ -0,0 +1,97 @@
@@ -0,0 +1,97 @@
|
||||
<template> |
||||
<span> |
||||
<v-tooltip bottom> |
||||
<template v-slot:activator="{ on, attrs }"> |
||||
<v-btn |
||||
class="ma-2" |
||||
outlined |
||||
color="primary" |
||||
@click="copyTextToClipboard(navBarURLs.m3uURL)" |
||||
v-bind="attrs" |
||||
v-on="on" |
||||
small |
||||
> |
||||
<v-icon>mdi-playlist-play</v-icon> M3U |
||||
</v-btn> |
||||
</template> |
||||
<span>{{ clickToCopyText }}</span> |
||||
</v-tooltip> |
||||
|
||||
<v-tooltip bottom> |
||||
<template v-slot:activator="{ on, attrs }"> |
||||
<v-btn |
||||
class="ma-2" |
||||
outlined |
||||
color="primary" |
||||
@click="copyTextToClipboard(navBarURLs.xmlURL)" |
||||
v-bind="attrs" |
||||
v-on="on" |
||||
small |
||||
> |
||||
<v-icon>mdi-xml</v-icon> XML |
||||
</v-btn> |
||||
</template> |
||||
<span>{{ clickToCopyText }}</span> |
||||
</v-tooltip> |
||||
|
||||
<v-btn |
||||
icon |
||||
color="secondary" |
||||
:href="navBarURLs.documentationURL" |
||||
target="_blank" |
||||
> |
||||
<v-icon>mdi-file-document</v-icon> |
||||
</v-btn> |
||||
|
||||
<v-btn |
||||
icon |
||||
color="secondary" |
||||
:href="navBarURLs.discordURL" |
||||
target="_blank" |
||||
> |
||||
<v-icon>mdi-discord</v-icon> |
||||
</v-btn> |
||||
|
||||
<v-btn |
||||
icon |
||||
color="secondary" |
||||
:href="navBarURLs.githubURL" |
||||
target="_blank" |
||||
> |
||||
<v-icon>mdi-github</v-icon> |
||||
</v-btn> |
||||
</span> |
||||
</template> |
||||
|
||||
<script> |
||||
import { mapState } from "pinia"; |
||||
import { applicationState } from "@/stores/applicationState"; |
||||
import { snackbarState } from "@/stores/snackbarState"; |
||||
|
||||
export default { |
||||
name: "ToolBarLinks.vue", |
||||
data: () => ({ |
||||
toast: false, |
||||
clickToCopyText: "Click to copy to clipboard!", |
||||
successCopyText: "Successfully copied text to clipboard!", |
||||
failedCopyText: "Failed to copy text to clipboard! Error: ", |
||||
}), |
||||
computed: { |
||||
...mapState(applicationState, ["navBarURLs"]), |
||||
...mapState(snackbarState, ["showSnackbar"]), |
||||
}, |
||||
methods: { |
||||
copyTextToClipboard(text) { |
||||
try { |
||||
navigator.clipboard.writeText(text); |
||||
this.showSnackbar(this.successCopyText); |
||||
} catch (e) { |
||||
console.error(e); |
||||
this.showSnackbar(`${this.failedCopyText}${e}`); |
||||
} |
||||
}, |
||||
}, |
||||
}; |
||||
</script> |
||||
|
||||
<style scoped></style> |
@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
|
||||
<template> |
||||
<v-text-field |
||||
outlined |
||||
class="ml-5" |
||||
label="Search..." |
||||
prepend-inner-icon="mdi-magnify" |
||||
hide-details |
||||
dense |
||||
></v-text-field> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: "ToolBarSearch", |
||||
}; |
||||
</script> |
||||
|
||||
<style scoped></style> |
@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
|
||||
<template> |
||||
<v-snackbar v-model="snackbar" :timeout="timeout"> |
||||
{{ currentMessage }} |
||||
<v-btn text color="primary" @click.native="closeSnackbar()" |
||||
>Close</v-btn |
||||
> |
||||
</v-snackbar> |
||||
</template> |
||||
|
||||
<script> |
||||
import { mapState } from "pinia"; |
||||
import { snackbarState } from "@/stores/snackbarState"; |
||||
|
||||
export default { |
||||
data: () => ({ |
||||
timeout: 4000, |
||||
}), |
||||
computed: { |
||||
...mapState(snackbarState, [ |
||||
"currentMessage", |
||||
"isVisible", |
||||
"closeSnackbar", |
||||
"openSnackbar", |
||||
]), |
||||
snackbar: { |
||||
get() { |
||||
return this.isVisible; |
||||
}, |
||||
set() { |
||||
this.closeSnackbar(); |
||||
}, |
||||
}, |
||||
}, |
||||
}; |
||||
</script> |
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
import Vue from "vue"; |
||||
import App from "./App.vue"; |
||||
import vuetify from "./plugins/vuetify"; |
||||
import router from "./router"; |
||||
import { createPinia, PiniaVuePlugin } from "pinia"; |
||||
import autoPageTitleMixin from "./mixins/autoPageTitle"; |
||||
import "roboto-fontface/css/roboto/roboto-fontface.css"; |
||||
import "@mdi/font/css/materialdesignicons.css"; |
||||
|
||||
Vue.config.productionTip = false; |
||||
|
||||
Vue.use(PiniaVuePlugin); |
||||
const pinia = createPinia(); |
||||
|
||||
// Mixin to automate the page title when navigating... Will default to "ErsatzTV if no title value exported from page.
|
||||
Vue.mixin(autoPageTitleMixin); |
||||
|
||||
new Vue({ |
||||
vuetify, |
||||
router, |
||||
pinia, |
||||
render: (h) => h(App), |
||||
}).$mount("#app"); |
@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
|
||||
function getTitle(vm) { |
||||
const { title } = vm.$options; |
||||
if (title) { |
||||
return typeof title === "function" ? title.call(vm) : title; |
||||
} |
||||
} |
||||
|
||||
export default { |
||||
created() { |
||||
const title = getTitle(this); |
||||
if (title) { |
||||
document.title = `ErsatzTV | ${title}`; |
||||
} |
||||
}, |
||||
}; |
@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
|
||||
import Vue from "vue"; |
||||
import Vuetify from "vuetify/lib/framework"; |
||||
import colors from "vuetify/lib/util/colors"; |
||||
|
||||
Vue.use(Vuetify); |
||||
|
||||
export default new Vuetify({ |
||||
icons: { |
||||
iconfont: "mdi", // default - only for display purposes
|
||||
}, |
||||
theme: { |
||||
themes: { |
||||
dark: { |
||||
primary: colors.yellow, |
||||
secondary: colors.teal.accent3, |
||||
accent: colors.yellow.accent2, |
||||
error: colors.red, |
||||
warning: colors.orange, |
||||
info: colors.lightBlue, |
||||
success: colors.green, |
||||
}, |
||||
}, |
||||
options: { |
||||
customProperties: true, |
||||
}, |
||||
dark: true, |
||||
}, |
||||
}); |
@ -0,0 +1,223 @@
@@ -0,0 +1,223 @@
|
||||
import Vue from "vue"; |
||||
import VueRouter from "vue-router"; |
||||
import HomePage from "../views/HomePage.vue"; |
||||
|
||||
Vue.use(VueRouter); |
||||
|
||||
const routes = [ |
||||
{ |
||||
path: "/", |
||||
name: "Home", |
||||
component: HomePage, |
||||
meta: { |
||||
icon: "mdi-home", |
||||
disabled: false, |
||||
}, |
||||
}, |
||||
{ |
||||
path: "/channels", |
||||
name: "Channels", |
||||
meta: { |
||||
icon: "mdi-broadcast", |
||||
disabled: true, |
||||
}, |
||||
}, |
||||
{ |
||||
path: "/ffmpeg-profiles", |
||||
name: "FFmpeg Profiles", |
||||
meta: { |
||||
icon: "mdi-video-input-component", |
||||
disabled: true, |
||||
}, |
||||
}, |
||||
{ |
||||
path: "/watermarks", |
||||
name: "Watermarks", |
||||
meta: { |
||||
icon: "mdi-watermark", |
||||
disabled: true, |
||||
}, |
||||
}, |
||||
{ |
||||
path: "/sources", |
||||
name: "Media Sources", |
||||
meta: { |
||||
icon: "mdi-server-network", |
||||
disabled: false, |
||||
}, |
||||
children: [ |
||||
{ |
||||
path: "/sources/local", |
||||
name: "Local", |
||||
meta: { |
||||
icon: "mdi-folder", |
||||
disabled: true, |
||||
}, |
||||
}, |
||||
{ |
||||
path: "/sources/emby", |
||||
name: "Emby", |
||||
meta: { |
||||
icon: "mdi-emby", |
||||
disabled: true, |
||||
}, |
||||
}, |
||||
{ |
||||
path: "/sources/jellyfin", |
||||
name: "Jellyfin", |
||||
meta: { |
||||
icon: "mdi-jellyfish", |
||||
disabled: true, |
||||
}, |
||||
}, |
||||
{ |
||||
path: "/sources/plex", |
||||
name: "Plex", |
||||
meta: { |
||||
icon: "mdi-plex", |
||||
disabled: true, |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
path: "/media", |
||||
name: "Media", |
||||
meta: { |
||||
icon: "mdi-cog", |
||||
disabled: false, |
||||
}, |
||||
children: [ |
||||
{ |
||||
path: "/media/libraries", |
||||
name: "Libraries", |
||||
meta: { |
||||
icon: "mdi-library", |
||||
disabled: true, |
||||
}, |
||||
}, |
||||
{ |
||||
path: "/media/trash", |
||||
name: "Trash", |
||||
meta: { |
||||
icon: "mdi-trash-can", |
||||
disabled: true, |
||||
}, |
||||
}, |
||||
{ |
||||
path: "/media/tv-shows", |
||||
name: "TV Shows", |
||||
meta: { |
||||
icon: "mdi-television-classic", |
||||
disabled: true, |
||||
}, |
||||
}, |
||||
{ |
||||
path: "/media/movies", |
||||
name: "Movies", |
||||
meta: { |
||||
icon: "mdi-movie", |
||||
disabled: true, |
||||
}, |
||||
}, |
||||
{ |
||||
path: "/media/music-videos", |
||||
name: "Music Videos", |
||||
meta: { |
||||
icon: "mdi-music-circle", |
||||
disabled: true, |
||||
}, |
||||
}, |
||||
{ |
||||
path: "/media/other-videos", |
||||
name: "Other Videos", |
||||
meta: { |
||||
icon: "mdi-video", |
||||
disabled: true, |
||||
}, |
||||
}, |
||||
{ |
||||
path: "/media/songs", |
||||
name: "Songs", |
||||
meta: { |
||||
icon: "mdi-album", |
||||
disabled: true, |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
path: "/lists", |
||||
name: "Lists", |
||||
meta: { |
||||
icon: "mdi-format-list-bulleted", |
||||
disabled: false, |
||||
}, |
||||
children: [ |
||||
{ |
||||
path: "/lists/collections", |
||||
name: "Collections", |
||||
meta: { |
||||
icon: "mdi-collage", |
||||
disabled: true, |
||||
}, |
||||
}, |
||||
{ |
||||
path: "/lists/trakt-lists", |
||||
name: "Trakt Lists", |
||||
meta: { |
||||
icon: "mdi-hammer", |
||||
disabled: true, |
||||
}, |
||||
}, |
||||
{ |
||||
path: "/lists/filler-presets", |
||||
name: "Filler Presets", |
||||
meta: { |
||||
icon: "mdi-tune-vertical", |
||||
disabled: true, |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
{ |
||||
path: "/schedules", |
||||
name: "Schedules", |
||||
meta: { |
||||
icon: "mdi-calendar", |
||||
disabled: true, |
||||
}, |
||||
}, |
||||
{ |
||||
path: "/playouts", |
||||
name: "Playouts", |
||||
meta: { |
||||
icon: "mdi-clipboard-play-multiple", |
||||
disabled: true, |
||||
}, |
||||
}, |
||||
{ |
||||
path: "/settings", |
||||
name: "Settings", |
||||
meta: { |
||||
icon: "mdi-cog", |
||||
disabled: true, |
||||
}, |
||||
}, |
||||
{ |
||||
path: "/Logs", |
||||
name: "Logs", |
||||
meta: { |
||||
icon: "mdi-card-text", |
||||
disabled: true, |
||||
}, |
||||
}, |
||||
]; |
||||
|
||||
const router = new VueRouter({ |
||||
mode: "history", |
||||
base: process.env.BASE_URL, |
||||
routes, |
||||
}); |
||||
|
||||
export default router; |
@ -0,0 +1,42 @@
@@ -0,0 +1,42 @@
|
||||
import { defineStore } from "pinia"; |
||||
|
||||
const pageURL = `${window.location}`; |
||||
|
||||
export const applicationState = defineStore("appState", { |
||||
state: () => { |
||||
return { |
||||
miniMenu: false, |
||||
currentVersion: "0.4.3-7cd2f9a-docker-nvidia", // Needs to be pulled from API with an action when ready
|
||||
m3uURL: pageURL + "iptv/channels.m3u", |
||||
xmlURL: pageURL + "iptv/xmltv.xml", |
||||
documentationURL: "https://ersatztv.org/", |
||||
githubURL: "https://github.com/jasongdove/ErsatzTV", |
||||
discordURL: "https://discord.gg/hHaJm3yGy6", |
||||
}; |
||||
}, |
||||
getters: { |
||||
isNavigationMini(state) { |
||||
return state.miniMenu; |
||||
}, |
||||
currentServerVersion(state) { |
||||
return state.currentVersion; |
||||
}, |
||||
navBarURLs(state) { |
||||
return { |
||||
m3uURL: state.m3uURL, |
||||
xmlURL: state.xmlURL, |
||||
documentationURL: state.documentationURL, |
||||
githubURL: state.githubURL, |
||||
discordURL: state.discordURL, |
||||
}; |
||||
}, |
||||
}, |
||||
actions: { |
||||
toggleMiniNavigation() { |
||||
this.miniMenu = !this.miniMenu; |
||||
}, |
||||
disableMiniNavigation() { |
||||
this.miniMenu = false; |
||||
}, |
||||
}, |
||||
}); |
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
import { defineStore } from "pinia"; |
||||
|
||||
export const snackbarState = defineStore("snackbarState", { |
||||
state: () => { |
||||
return { |
||||
visible: false, |
||||
message: "", |
||||
}; |
||||
}, |
||||
getters: { |
||||
isVisible(state) { |
||||
return state.visible; |
||||
}, |
||||
currentMessage(state) { |
||||
return state.message; |
||||
}, |
||||
}, |
||||
actions: { |
||||
showSnackbar(message) { |
||||
this.message = message; |
||||
this.visible = true; |
||||
}, |
||||
closeSnackbar() { |
||||
this.visible = false; |
||||
}, |
||||
}, |
||||
}); |
@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
|
||||
<template> |
||||
<div> |
||||
<h1>{{ this.$route.name }}</h1> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
title() { |
||||
return `Home`; |
||||
}, |
||||
}; |
||||
</script> |
@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
|
||||
const { defineConfig } = require("@vue/cli-service"); |
||||
|
||||
module.exports = defineConfig({ |
||||
transpileDependencies: ["vuetify"], |
||||
runtimeCompiler: true, |
||||
|
||||
pwa: { |
||||
name: "ErsatzTV", |
||||
}, |
||||
publicPath: "/v2/", |
||||
outputDir: "../wwwroot/v2", |
||||
filenameHashing: false |
||||
}); |
@ -0,0 +1,92 @@
@@ -0,0 +1,92 @@
|
||||
{ |
||||
"uuid": "49d843b2-cad8-4e26-a7f7-e2081683ee0e", |
||||
"lastMigration": 19, |
||||
"name": "ErsatzTV (copy)", |
||||
"endpointPrefix": "", |
||||
"latency": 0, |
||||
"port": 3000, |
||||
"hostname": "0.0.0.0", |
||||
"routes": [ |
||||
{ |
||||
"uuid": "56cb284d-6911-4695-a8ca-eecffbfcbd57", |
||||
"documentation": "get all channels", |
||||
"method": "get", |
||||
"endpoint": "api/channels", |
||||
"responses": [ |
||||
{ |
||||
"uuid": "4fc6ab6f-6a1b-4084-8bb2-94f00d62840c", |
||||
"body": "[\n {{#repeat (faker 'datatype.number' min=1 max=5)}}\n { \n \"id\": {{@index}},\n \"number\": {{faker 'datatype.number' min=1 max=25}},\n \"name\": \"{{faker 'hacker.adjective'}} {{faker 'hacker.noun'}}\",\n \"group\": \"\",\n \"categories\": [],\n \"ffmpegProfileId\": 1,\n \"logo\": \"\",\n \"language\": \"{{oneOf (array '' 'eng')}}\",\n \"streamingMode\": \"{{oneOf (array 'HLS Segmenter' 'MPEG-TS')}}\"\n }\n {{/repeat}}\n]", |
||||
"latency": 0, |
||||
"statusCode": 200, |
||||
"label": "", |
||||
"headers": [], |
||||
"filePath": "", |
||||
"sendFileAsBody": false, |
||||
"rules": [], |
||||
"rulesOperator": "OR", |
||||
"disableTemplating": false, |
||||
"fallbackTo404": false |
||||
} |
||||
], |
||||
"enabled": true, |
||||
"randomResponse": false, |
||||
"sequentialResponse": false |
||||
}, |
||||
{ |
||||
"uuid": "a87f888a-2038-495e-9f89-2ae32b854004", |
||||
"documentation": "get all ffmpeg profiles", |
||||
"method": "get", |
||||
"endpoint": "api/ffmpeg/profiles", |
||||
"responses": [ |
||||
{ |
||||
"uuid": "2f42cd38-2591-475f-a4bf-e5fb3455a8b3", |
||||
"body": "[\n {{#repeat (faker 'datatype.number' min=2 max=3)}}\n { \n \"id\": {{@index}},\n \"name\": \"{{faker 'hacker.adjective'}} {{faker 'hacker.noun'}}\",\n \"transcode\": {{faker 'datatype.boolean'}},\n \"resolution\": \"{{oneOf (array '1920x1080' '1280x720' '720x480')}}\",\n \"videoCodec\": \"{{oneOf (array 'hevc_nvenc' 'h264_nvenc')}}\",\n \"audioCodec\": \"{{oneOf (array 'aac' 'ac3')}}\"\n }\n {{/repeat}}\n]", |
||||
"latency": 0, |
||||
"statusCode": 200, |
||||
"label": "", |
||||
"headers": [], |
||||
"filePath": "", |
||||
"sendFileAsBody": false, |
||||
"rules": [], |
||||
"rulesOperator": "OR", |
||||
"disableTemplating": false, |
||||
"fallbackTo404": false |
||||
} |
||||
], |
||||
"enabled": true, |
||||
"randomResponse": false, |
||||
"sequentialResponse": false |
||||
} |
||||
], |
||||
"proxyMode": false, |
||||
"proxyHost": "", |
||||
"proxyRemovePrefix": false, |
||||
"tlsOptions": { |
||||
"enabled": false, |
||||
"type": "CERT", |
||||
"pfxPath": "", |
||||
"certPath": "", |
||||
"keyPath": "", |
||||
"caPath": "", |
||||
"passphrase": "" |
||||
}, |
||||
"cors": true, |
||||
"headers": [ |
||||
{ |
||||
"key": "Content-Type", |
||||
"value": "application/json" |
||||
} |
||||
], |
||||
"proxyReqHeaders": [ |
||||
{ |
||||
"key": "", |
||||
"value": "" |
||||
} |
||||
], |
||||
"proxyResHeaders": [ |
||||
{ |
||||
"key": "", |
||||
"value": "" |
||||
} |
||||
] |
||||
} |
Loading…
Reference in new issue