mirror of https://github.com/ErsatzTV/ErsatzTV.git
60 changed files with 2 additions and 25553 deletions
@ -1,22 +0,0 @@
@@ -1,22 +0,0 @@
|
||||
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@v4 |
||||
# Setup NodeJS version 16 |
||||
- name: Setup NodeJS V16.x.x |
||||
uses: actions/setup-node@v3 |
||||
with: |
||||
node-version: '16' |
||||
# 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 |
@ -1,2 +0,0 @@
@@ -1,2 +0,0 @@
|
||||
VUE_APP_I18N_LOCALE=en |
||||
VUE_APP_I18N_FALLBACK_LOCALE=en |
@ -1,28 +0,0 @@
@@ -1,28 +0,0 @@
|
||||
.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? |
||||
|
||||
# generated files |
||||
src/assets/css/*.css |
||||
src/models/*.js |
||||
src/models/*.js.map |
@ -1,5 +0,0 @@
@@ -1,5 +0,0 @@
|
||||
{ |
||||
"semi": true, |
||||
"singleQuote": true, |
||||
"trailingComma": "none" |
||||
} |
@ -1,27 +0,0 @@
@@ -1,27 +0,0 @@
|
||||
# ErsatzTV Vuetify UI - WIP |
||||
|
||||
## Project setup |
||||
``` |
||||
npm install |
||||
``` |
||||
|
||||
### Compiles and hot-reloads for development |
||||
``` |
||||
npm run serve |
||||
``` |
||||
|
||||
To set a custom ErsatzTV api URL, use the following environment variable `VUE_APP_ETV_BASE_URL`. |
||||
|
||||
### 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/). |
||||
|
@ -1,3 +0,0 @@
@@ -1,3 +0,0 @@
|
||||
module.exports = { |
||||
presets: ['@vue/cli-plugin-babel/preset'] |
||||
}; |
@ -1,19 +0,0 @@
@@ -1,19 +0,0 @@
|
||||
{ |
||||
"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
@ -1,80 +0,0 @@
@@ -1,80 +0,0 @@
|
||||
{ |
||||
"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", |
||||
"i18n:report": "vue-cli-service i18n:report --src \"./src/**/*.?(js|vue)\" --locales \"./src/locales/**/*.json\"" |
||||
}, |
||||
"dependencies": { |
||||
"@mdi/font": "5.9.55", |
||||
"@sweetalert2/theme-dark": "^5.0.11", |
||||
"axios": "^0.26.1", |
||||
"core-js": "^3.8.3", |
||||
"pinia": "^2.0.11", |
||||
"pinia-plugin-persistedstate": "^1.5.1", |
||||
"roboto-fontface": "*", |
||||
"sweetalert2": "^11.4.17", |
||||
"vue": "^2.6.14", |
||||
"vue-class-component": "^7.2.6", |
||||
"vue-i18n": "^8.26.3", |
||||
"vue-property-decorator": "^9.1.2", |
||||
"vue-router": "^3.2.0", |
||||
"vue-sweetalert2": "^5.0.5", |
||||
"vuetify": "^2.6.0" |
||||
}, |
||||
"devDependencies": { |
||||
"@babel/core": "^7.12.16", |
||||
"@babel/eslint-parser": "^7.12.16", |
||||
"@typescript-eslint/eslint-plugin": "^5.4.0", |
||||
"@typescript-eslint/parser": "^5.4.0", |
||||
"@vue/cli-plugin-babel": "~5.0.0", |
||||
"@vue/cli-plugin-eslint": "~5.0.0", |
||||
"@vue/cli-plugin-typescript": "~5.0.0", |
||||
"@vue/cli-service": "~5.0.0", |
||||
"@vue/composition-api": "^1.4.9", |
||||
"@vue/eslint-config-typescript": "^9.1.0", |
||||
"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", |
||||
"typescript": "~4.5.5", |
||||
"vue-cli-plugin-i18n": "~2.3.1", |
||||
"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", |
||||
"@vue/typescript" |
||||
], |
||||
"parserOptions": { |
||||
"parser": "@typescript-eslint/parser" |
||||
}, |
||||
"rules": { |
||||
"prettier/prettier": [ |
||||
"error", |
||||
{ |
||||
"endOfLine": "auto", |
||||
"tabWidth": 4 |
||||
} |
||||
] |
||||
} |
||||
}, |
||||
"browserslist": [ |
||||
"> 1%", |
||||
"last 2 versions", |
||||
"not dead" |
||||
] |
||||
} |
Before Width: | Height: | Size: 15 KiB |
@ -1,17 +0,0 @@
@@ -1,17 +0,0 @@
|
||||
<!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> |
@ -1,36 +0,0 @@
@@ -1,36 +0,0 @@
|
||||
<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-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> |
@ -1,12 +0,0 @@
@@ -1,12 +0,0 @@
|
||||
.swal2-container.swal2-center > .swal2-popup { |
||||
background-color: #1E1E1E !important; |
||||
border-color: green; |
||||
border-width: thin; |
||||
border-style: solid; |
||||
width: fit-content; |
||||
} |
||||
|
||||
.swal2-html-container, .swal2-title { |
||||
color: white !important; |
||||
font-family:Cambria; |
||||
} |
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 2.7 KiB |
@ -1,44 +0,0 @@
@@ -1,44 +0,0 @@
|
||||
<template> |
||||
<v-select |
||||
v-on:change="setUserLanguage($event)" |
||||
:value="language" |
||||
dense |
||||
outlined |
||||
:items="langs" |
||||
:label="$t('sidebar.languages')" |
||||
class="ma-2" |
||||
></v-select> |
||||
</template> |
||||
|
||||
<script> |
||||
import { mapState } from 'pinia'; |
||||
import { languageState } from '@/stores/languageState'; |
||||
|
||||
export default { |
||||
name: 'language-changer', |
||||
data() { |
||||
return { |
||||
langs: [ |
||||
{ text: this.$t('languages-code.en'), value: 'en' }, |
||||
{ text: this.$t('languages-code.pt-br'), value: 'pt-br' } |
||||
] |
||||
}; |
||||
}, |
||||
computed: { |
||||
...mapState(languageState, ['language', 'setLanguage']), |
||||
languageCode: { |
||||
get() { |
||||
return languageState.language; |
||||
}, |
||||
set(value) { |
||||
this.setLanguage(value); |
||||
} |
||||
} |
||||
}, |
||||
methods: { |
||||
setUserLanguage: ($event) => { |
||||
languageState().setLanguage($event); |
||||
} |
||||
} |
||||
}; |
||||
</script> |
@ -1,27 +0,0 @@
@@ -1,27 +0,0 @@
|
||||
<template> |
||||
<span> |
||||
<v-img |
||||
v-if="!isNavigationMini" |
||||
src="@/assets/images/ersatztv.svg" |
||||
class="ma-3" |
||||
></v-img> |
||||
<v-img |
||||
v-if="isNavigationMini" |
||||
src="@/assets/images/ersatztv-logo.svg" |
||||
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> |
@ -1,43 +0,0 @@
@@ -1,43 +0,0 @@
|
||||
<template> |
||||
<v-list nav dense> |
||||
<span v-for="(nav, i) in navigation" :key="i"> |
||||
<SideBarMenuItem |
||||
v-if="(!nav.children || !nav.showchildren) && !nav.meta.hidden" |
||||
:name="nav.name" |
||||
:path="nav.path" |
||||
:icon="nav.meta.icon" |
||||
:disabled="nav.meta.disabled" |
||||
/> |
||||
<SideBarMenuItemExpandable |
||||
v-else-if="nav.children" |
||||
@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> |
@ -1,37 +0,0 @@
@@ -1,37 +0,0 @@
|
||||
<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="$t(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> |
@ -1,73 +0,0 @@
@@ -1,73 +0,0 @@
|
||||
<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="$t(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="$t(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 |
||||
}, |
||||
nonmenuchildren: { |
||||
type: Array, |
||||
required: false |
||||
} |
||||
}, |
||||
data: () => ({ |
||||
opened: false |
||||
}), |
||||
computed: { |
||||
menuItemOpened: { |
||||
get: function () { |
||||
return this.opened; |
||||
}, |
||||
set: function () { |
||||
return !this.opened; |
||||
} |
||||
} |
||||
} |
||||
}; |
||||
</script> |
@ -1,28 +0,0 @@
@@ -1,28 +0,0 @@
|
||||
<template> |
||||
<span v-if="!isNavigationMini" class="text-center"> |
||||
<v-divider inset></v-divider> |
||||
<h4 class="pt-2">ErsatzTV {{ $t('sidebar.version') }}</h4> |
||||
|
||||
<p>{{ currentServerVersion }}</p> |
||||
</span> |
||||
</template> |
||||
|
||||
<script> |
||||
import { mapState } from 'pinia'; |
||||
import { applicationState } from '@/stores/applicationState'; |
||||
|
||||
export default { |
||||
name: 'SideBarVersion', |
||||
setup() { |
||||
applicationState().getVersion(); |
||||
}, |
||||
computed: { |
||||
...mapState(applicationState, [ |
||||
'currentServerVersion', |
||||
'isNavigationMini' |
||||
]) |
||||
} |
||||
}; |
||||
</script> |
||||
|
||||
<style scoped></style> |
@ -1,53 +0,0 @@
@@ -1,53 +0,0 @@
|
||||
<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> |
||||
<SideBarLanguageSelect /> |
||||
<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'; |
||||
import SideBarLanguageSelect from './SideBarLanguageSelect.vue'; |
||||
|
||||
export default { |
||||
name: 'NavToolbar', |
||||
components: { |
||||
SideBarMenu, |
||||
SideBarLogo, |
||||
SideBarVersion, |
||||
ToolBarLinks, |
||||
ToolBarSearch, |
||||
SideBarLanguageSelect |
||||
}, |
||||
computed: { |
||||
...mapState(applicationState, [ |
||||
'isNavigationMini', |
||||
'toggleMiniNavigation' |
||||
]) |
||||
} |
||||
}; |
||||
</script> |
@ -1,96 +0,0 @@
@@ -1,96 +0,0 @@
|
||||
<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>{{ $t('tool-bar.click-to-copy') }}</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>{{ $t('tool-bar.click-to-copy') }}</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 |
||||
}), |
||||
computed: { |
||||
...mapState(applicationState, ['navBarURLs']), |
||||
...mapState(snackbarState, ['showSnackbar']) |
||||
}, |
||||
methods: { |
||||
copyTextToClipboard(text) { |
||||
try { |
||||
navigator.clipboard.writeText(text); |
||||
this.showSnackbar(this.$t('tool-bar.copy-success')); |
||||
} catch (error) { |
||||
console.error(error); |
||||
this.showSnackbar( |
||||
this.$t('tool-bar.copy-failure', { message: error }) |
||||
); |
||||
} |
||||
} |
||||
} |
||||
}; |
||||
</script> |
||||
|
||||
<style scoped></style> |
@ -1,18 +0,0 @@
@@ -1,18 +0,0 @@
|
||||
<template> |
||||
<v-text-field |
||||
outlined |
||||
class="ml-5" |
||||
:label="$t('tool-bar.search')" |
||||
prepend-inner-icon="mdi-magnify" |
||||
hide-details |
||||
dense |
||||
></v-text-field> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: 'ToolBarSearch' |
||||
}; |
||||
</script> |
||||
|
||||
<style scoped></style> |
@ -1,35 +0,0 @@
@@ -1,35 +0,0 @@
|
||||
<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> |
@ -1,206 +0,0 @@
@@ -1,206 +0,0 @@
|
||||
{ |
||||
"$vuetify": { |
||||
"badge": "Badge", |
||||
"close": "Close", |
||||
"dataIterator": { |
||||
"noResultsText": "No matching records found", |
||||
"loadingText": "Loading items..." |
||||
}, |
||||
"dataTable": { |
||||
"itemsPerPageText": "Rows per page:", |
||||
"ariaLabel": { |
||||
"sortDescending": "Sorted descending.", |
||||
"sortAscending": "Sorted ascending.", |
||||
"sortNone": "Not sorted.", |
||||
"activateNone": "Activate to remove sorting.", |
||||
"activateDescending": "Activate to sort descending.", |
||||
"activateAscending": "Activate to sort ascending." |
||||
}, |
||||
"sortBy": "Sort by" |
||||
}, |
||||
"dataFooter": { |
||||
"itemsPerPageText": "Items per page:", |
||||
"itemsPerPageAll": "All", |
||||
"nextPage": "Next page", |
||||
"prevPage": "Previous page", |
||||
"firstPage": "First page", |
||||
"lastPage": "Last page", |
||||
"pageText": "{0}-{1} of {2}" |
||||
}, |
||||
"datePicker": { |
||||
"itemsSelected": "{0} selected", |
||||
"nextMonthAriaLabel": "Next month", |
||||
"nextYearAriaLabel": "Next year", |
||||
"prevMonthAriaLabel": "Previous month", |
||||
"prevYearAriaLabel": "Previous year" |
||||
}, |
||||
"noDataText": "No data available", |
||||
"carousel": { |
||||
"prev": "Previous visual", |
||||
"next": "Next visual", |
||||
"ariaLabel": { |
||||
"delimiter": "Carousel slide {0} of {1}" |
||||
} |
||||
}, |
||||
"calendar": { |
||||
"moreEvents": "{0} more" |
||||
}, |
||||
"fileInput": { |
||||
"counter": "{0} files", |
||||
"counterSize": "{0} files ({1} in total)" |
||||
}, |
||||
"timePicker": { |
||||
"am": "AM", |
||||
"pm": "PM" |
||||
}, |
||||
"pagination": { |
||||
"ariaLabel": { |
||||
"wrapper": "Pagination Navigation", |
||||
"next": "Next page", |
||||
"previous": "Previous page", |
||||
"page": "Goto Page {0}", |
||||
"currentPage": "Current Page, Page {0}" |
||||
} |
||||
}, |
||||
"rating": { |
||||
"ariaLabel": { |
||||
"icon": "Rating {0} of {1}" |
||||
} |
||||
} |
||||
}, |
||||
"languages-code": { |
||||
"en": "English", |
||||
"pt-br": "Portuguese (Brazil)" |
||||
}, |
||||
"tool-bar": { |
||||
"search": "Search...", |
||||
"click-to-copy": "Click to copy to clipboard!", |
||||
"copy-success": "Successfully copied text to clipboard!", |
||||
"copy-failure": "Failed to copy text to clipboard! Error: {message}" |
||||
}, |
||||
"sidebar": { |
||||
"languages": "Languages", |
||||
"version": "Version" |
||||
}, |
||||
"home": { |
||||
"title": "Home" |
||||
}, |
||||
"ffmpeg-profiles": { |
||||
"title": "FFmpeg Profiles", |
||||
"add-profile": "Add FFmpeg Profile", |
||||
"actions": "Actions", |
||||
"delete-dialog-title": "Are you sure?", |
||||
"delete-dialog-text": "Delete {profileName} FFmpeg Profile?", |
||||
"no": "No", |
||||
"yes": "Yes", |
||||
"profile-deleted": "FFmpeg Profile deleted!", |
||||
"table": { |
||||
"name": "Name", |
||||
"resolution": "Resolution", |
||||
"video": "Video", |
||||
"audio": "Audio" |
||||
} |
||||
}, |
||||
"edit-ffmpeg-profile": { |
||||
"General": "General", |
||||
"Video": "Video", |
||||
"Audio": "Audio", |
||||
"Name": "Name", |
||||
"thread-count": "Thread Count", |
||||
"preferred-resolution": "Preferred Resolution", |
||||
"format": "Format", |
||||
"bitrate": "Bitrate", |
||||
"buffer-size": "Buffer Size", |
||||
"hardware-acceleration": "Hardware Acceleration", |
||||
"vaapi-driver": "VAAPI Driver", |
||||
"vaapi-device": "VAAPI Device", |
||||
"normalize-framerate": "Normalize Framerate", |
||||
"auto-deinterlace-video": "Auto Deinterlace Video", |
||||
"channels": "Channels", |
||||
"sample-rate": "Sample Rate", |
||||
"normalize-loudness": "Normalize Loudness", |
||||
"save-profile": "Save Profile", |
||||
"cancel": "Cancel", |
||||
"help": "Help", |
||||
"add-profile": "Add FFmpeg Profile", |
||||
"edit-profile": "Edit FFmpeg Profile", |
||||
"profile-saved": "FFmpeg Profile Saved!" |
||||
}, |
||||
"watermarks": { |
||||
"title": "Watermarks" |
||||
}, |
||||
"media-sources": { |
||||
"title": "Media Sources", |
||||
"local": { |
||||
"title": "Local" |
||||
}, |
||||
"emby": { |
||||
"title": "Emby" |
||||
}, |
||||
"jellyfin": { |
||||
"title": "Jellyfin" |
||||
}, |
||||
"plex": { |
||||
"title": "Plex" |
||||
} |
||||
}, |
||||
"lists": { |
||||
"title": "Lists", |
||||
"collections": { |
||||
"title": "Collections" |
||||
}, |
||||
"trakt-lists": { |
||||
"title": "Trakt Lists" |
||||
}, |
||||
"filler-presets": { |
||||
"title": "Filler Presets" |
||||
} |
||||
}, |
||||
"schedules": { |
||||
"title": "Schedules" |
||||
}, |
||||
"playouts": { |
||||
"title": "Playouts" |
||||
}, |
||||
"settings": { |
||||
"title": "Settings" |
||||
}, |
||||
"logs": { |
||||
"title": "Logs" |
||||
}, |
||||
"media": { |
||||
"title": "Media", |
||||
"libraries": { |
||||
"title": "Libraries" |
||||
}, |
||||
"trash": { |
||||
"title": "Trash" |
||||
}, |
||||
"tv-shows": { |
||||
"title": "TV Shows" |
||||
}, |
||||
"movies": { |
||||
"title": "Movies" |
||||
}, |
||||
"music-videos": { |
||||
"title": "Music Videos" |
||||
}, |
||||
"other-videos": { |
||||
"title": "Other Videos" |
||||
}, |
||||
"songs": { |
||||
"title": "Songs" |
||||
} |
||||
}, |
||||
"channels": { |
||||
"title": "Channels", |
||||
"table": { |
||||
"number": "Number", |
||||
"logo": "Logo", |
||||
"name": "Name", |
||||
"language": "Language", |
||||
"mode": "Mode", |
||||
"ffmpeg-profile": "FFmpeg Profile" |
||||
} |
||||
} |
||||
} |
@ -1,207 +0,0 @@
@@ -1,207 +0,0 @@
|
||||
{ |
||||
"$vuetify": { |
||||
"badge": "Distintivo", |
||||
"close": "Fechar", |
||||
"Actions": "Ações", |
||||
"dataIterator": { |
||||
"noResultsText": "Nenhum dado encontrado", |
||||
"loadingText": "Carregando itens..." |
||||
}, |
||||
"dataTable": { |
||||
"itemsPerPageText": "Linhas por página:", |
||||
"ariaLabel": { |
||||
"sortDescending": "Ordenado decrescente.", |
||||
"sortAscending": "Ordenado crescente.", |
||||
"sortNone": "Não ordenado.", |
||||
"activateNone": "Ative para remover a ordenação.", |
||||
"activateDescending": "Ative para ordenar decrescente.", |
||||
"activateAscending": "Ative para ordenar crescente." |
||||
}, |
||||
"sortBy": "Ordenar por" |
||||
}, |
||||
"dataFooter": { |
||||
"itemsPerPageText": "Itens por página:", |
||||
"itemsPerPageAll": "Todos", |
||||
"nextPage": "Próxima página", |
||||
"prevPage": "Página anterior", |
||||
"firstPage": "Primeira página", |
||||
"lastPage": "Última página", |
||||
"pageText": "{0}-{1} de {2}" |
||||
}, |
||||
"datePicker": { |
||||
"itemsSelected": "{0} selecionado(s)", |
||||
"nextMonthAriaLabel": "Próximo mês", |
||||
"nextYearAriaLabel": "Próximo ano", |
||||
"prevMonthAriaLabel": "Mês anterior", |
||||
"prevYearAriaLabel": "Ano anterior" |
||||
}, |
||||
"noDataText": "Não há dados disponíveis", |
||||
"carousel": { |
||||
"prev": "Visão anterior", |
||||
"next": "Próxima visão", |
||||
"ariaLabel": { |
||||
"delimiter": "Slide {0} de {1} do carrossel" |
||||
} |
||||
}, |
||||
"calendar": { |
||||
"moreEvents": "Mais {0}" |
||||
}, |
||||
"fileInput": { |
||||
"counter": "{0} arquivo(s)", |
||||
"counterSize": "{0} arquivo(s) ({1} no total)" |
||||
}, |
||||
"timePicker": { |
||||
"am": "AM", |
||||
"pm": "PM" |
||||
}, |
||||
"pagination": { |
||||
"ariaLabel": { |
||||
"wrapper": "Navegação de paginação", |
||||
"next": "Próxima página", |
||||
"previous": "Página anterior", |
||||
"page": "Ir à página {0}", |
||||
"currentPage": "Página atual, página {0}" |
||||
} |
||||
}, |
||||
"rating": { |
||||
"ariaLabel": { |
||||
"icon": "Rating {0} de {1}" |
||||
} |
||||
} |
||||
}, |
||||
"languages-code": { |
||||
"en": "Inglês", |
||||
"pt-br": "Português (Brasil)" |
||||
}, |
||||
"tool-bar": { |
||||
"search": "Busca...", |
||||
"click-to-copy": "Clique para copiar para a área de transferência!", |
||||
"copy-success": "Texto copiado com sucesso para a área de transferência!", |
||||
"copy-failure": "Falha ao copiar o texto para a área de transferência! Erro: {message}" |
||||
}, |
||||
"sidebar": { |
||||
"languages": "Línguas", |
||||
"version": "Versão" |
||||
}, |
||||
"home": { |
||||
"title": "Início" |
||||
}, |
||||
"ffmpeg-profiles": { |
||||
"title": "Perfis FFmpeg", |
||||
"add-profile": "Adicionar perfil FFmpeg", |
||||
"actions": "Ações", |
||||
"delete-dialog-title": "Tem certeza?", |
||||
"delete-dialog-text": "Excluir perfil {profileName} do FFmpeg?", |
||||
"no": "Não", |
||||
"yes": "Sim", |
||||
"profile-deleted": "Perfil do FFmpeg excluído!", |
||||
"table": { |
||||
"name": "Nome", |
||||
"resolution": "Resolução", |
||||
"video": "Vídeo", |
||||
"audio": "Áudio" |
||||
} |
||||
}, |
||||
"edit-ffmpeg-profile": { |
||||
"General": "Em geral", |
||||
"Video": "Vídeo", |
||||
"Audio": "Áudio", |
||||
"Name": "Nome", |
||||
"thread-count": "Contagem de fios", |
||||
"preferred-resolution": "Resolução preferencial", |
||||
"format": "Formato", |
||||
"bitrate": "Taxa de bits", |
||||
"buffer-size": "Tamanho do buffer", |
||||
"hardware-acceleration": "Aceleraçao do hardware", |
||||
"vaapi-driver": "Driver VAAPI", |
||||
"vaapi-device": "Dispositivo VAAPI", |
||||
"normalize-framerate": "Normalizar taxa de quadros", |
||||
"auto-deinterlace-video": "Vídeo de desentrelaçamento automático", |
||||
"channels": "Canais", |
||||
"sample-rate": "Taxa de amostragem", |
||||
"normalize-loudness": "Normalizar volume", |
||||
"save-profile": "Salvar perfil", |
||||
"cancel": "Cancelar", |
||||
"help": "Ajuda", |
||||
"add-profile": "Adicionar perfil FFmpeg", |
||||
"edit-profile": "Editar perfil do FFmpeg", |
||||
"profile-saved": "Perfil do FFmpeg salvo!" |
||||
}, |
||||
"watermarks": { |
||||
"title": "Marcas d'água" |
||||
}, |
||||
"media-sources": { |
||||
"title": "Fontes de mídia", |
||||
"local": { |
||||
"title": "Local" |
||||
}, |
||||
"emby": { |
||||
"title": "Emby" |
||||
}, |
||||
"jellyfin": { |
||||
"title": "Jellyfin" |
||||
}, |
||||
"plex": { |
||||
"title": "Plex" |
||||
} |
||||
}, |
||||
"lists": { |
||||
"title": "Listas", |
||||
"collections": { |
||||
"title": "Coleções" |
||||
}, |
||||
"trakt-lists": { |
||||
"title": "Listas Trakt" |
||||
}, |
||||
"filler-presets": { |
||||
"title": "Predefinições de preenchimento" |
||||
} |
||||
}, |
||||
"schedules": { |
||||
"title": "Horários" |
||||
}, |
||||
"playouts": { |
||||
"title": "Playouts" |
||||
}, |
||||
"settings": { |
||||
"title": "Definições" |
||||
}, |
||||
"logs": { |
||||
"title": "Logs" |
||||
}, |
||||
"media": { |
||||
"title": "Mídias", |
||||
"libraries": { |
||||
"title": "Bibliotecas" |
||||
}, |
||||
"trash": { |
||||
"title": "Lixo" |
||||
}, |
||||
"tv-shows": { |
||||
"title": "Séries" |
||||
}, |
||||
"movies": { |
||||
"title": "Filmes" |
||||
}, |
||||
"music-videos": { |
||||
"title": "Vídeos de música" |
||||
}, |
||||
"other-videos": { |
||||
"title": "Other Videos" |
||||
}, |
||||
"songs": { |
||||
"title": "Songs" |
||||
} |
||||
}, |
||||
"channels": { |
||||
"title": "Canais", |
||||
"table": { |
||||
"number": "Numero", |
||||
"logo": "Logo", |
||||
"name": "Nome", |
||||
"language": "Idioma", |
||||
"mode": "Modo", |
||||
"ffmpeg-profile": "Perfil FFmpeg" |
||||
} |
||||
} |
||||
} |
@ -1,37 +0,0 @@
@@ -1,37 +0,0 @@
|
||||
import Vue from 'vue'; |
||||
import App from './App.vue'; |
||||
import vuetify from './plugins/vuetify'; |
||||
import router from './router'; |
||||
import { createPinia, PiniaVuePlugin } from 'pinia'; |
||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'; |
||||
import autoPageTitleMixin from './mixins/autoPageTitle'; |
||||
import 'roboto-fontface/css/roboto/roboto-fontface.css'; |
||||
import '@mdi/font/css/materialdesignicons.css'; |
||||
import i18n from './plugins/i18n'; |
||||
import VueSweetalert2 from 'vue-sweetalert2'; |
||||
import 'sweetalert2/dist/sweetalert2.min.css'; |
||||
import './assets/css/global.scss'; |
||||
|
||||
Vue.config.productionTip = false; |
||||
|
||||
Vue.use(PiniaVuePlugin); |
||||
const pinia = createPinia(); |
||||
pinia.use(piniaPluginPersistedstate); |
||||
// Mixin to automate the page title when navigating... Will default to "ErsatzTV if no title value exported from page.
|
||||
Vue.mixin(autoPageTitleMixin); |
||||
|
||||
const options = { |
||||
confirmButtonColor: '#1E1E1E', |
||||
cancelButtonColor: '#1E1E1E', |
||||
background: '#1E1E1E', |
||||
iconColor: '#4CAF50' |
||||
}; |
||||
Vue.use(VueSweetalert2, options); |
||||
|
||||
new Vue({ |
||||
vuetify, |
||||
router, |
||||
pinia, |
||||
i18n, |
||||
render: (h) => h(App) |
||||
}).$mount('#app'); |
@ -1,18 +0,0 @@
@@ -1,18 +0,0 @@
|
||||
function getTitle(vm) { |
||||
const { title } = vm.$options; |
||||
if (title) { |
||||
return typeof title === 'function' ? title.call(vm) : title; |
||||
} |
||||
if (vm._data.title) { |
||||
return vm._data.title; |
||||
} |
||||
} |
||||
|
||||
export default { |
||||
created() { |
||||
const title = getTitle(this); |
||||
if (title) { |
||||
document.title = `ErsatzTV | ${title}`; |
||||
} |
||||
} |
||||
}; |
@ -1,6 +0,0 @@
@@ -1,6 +0,0 @@
|
||||
export interface Channel { |
||||
id: number; |
||||
number: string; |
||||
name: string; |
||||
streamingMode: string; |
||||
} |
@ -1,20 +0,0 @@
@@ -1,20 +0,0 @@
|
||||
export interface FFmpegFullProfile { |
||||
Id: number; |
||||
name: string; |
||||
threadCount: number; |
||||
hardwareAcceleration: number; |
||||
vaapiDriver: number; |
||||
vaapiDevice: string; |
||||
resolutionId: number; |
||||
videoFormat: number; |
||||
videoBitrate: number; |
||||
videoBufferSize: number; |
||||
audioFormat: number; |
||||
audioBitrate: number; |
||||
audioBufferSize: number; |
||||
normalizeLoudness: boolean; |
||||
audioChannels: number; |
||||
audioSampleRate: number; |
||||
normalizeFramerate: boolean; |
||||
deinterlaceVideo: boolean; |
||||
} |
@ -1,7 +0,0 @@
@@ -1,7 +0,0 @@
|
||||
export interface FFmpegProfile { |
||||
id: number; |
||||
name: string; |
||||
resolution: string; |
||||
video: string; |
||||
audio: string; |
||||
} |
@ -1,27 +0,0 @@
@@ -1,27 +0,0 @@
|
||||
import Vue from 'vue'; |
||||
import VueI18n, { LocaleMessages } from 'vue-i18n'; |
||||
|
||||
Vue.use(VueI18n); |
||||
|
||||
function loadLocaleMessages(): LocaleMessages { |
||||
const locales = require.context( |
||||
'../locales', |
||||
true, |
||||
/[A-Za-z0-9-_,\s]+\.json$/i |
||||
); |
||||
const messages: LocaleMessages = {}; |
||||
locales.keys().forEach((key) => { |
||||
const matched = key.match(/([A-Za-z0-9-_]+)\./i); |
||||
if (matched && matched.length > 1) { |
||||
const locale = matched[1]; |
||||
messages[locale] = locales(key); |
||||
} |
||||
}); |
||||
return messages; |
||||
} |
||||
|
||||
export default new VueI18n({ |
||||
locale: process.env.VUE_APP_I18N_LOCALE || 'en', |
||||
fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en', |
||||
messages: loadLocaleMessages() |
||||
}); |
@ -1,33 +0,0 @@
@@ -1,33 +0,0 @@
|
||||
import Vue from 'vue'; |
||||
import Vuetify from 'vuetify'; |
||||
import colors from 'vuetify/lib/util/colors'; |
||||
import VueI18n from './i18n'; |
||||
|
||||
Vue.use(Vuetify); |
||||
|
||||
export default new Vuetify({ |
||||
icons: { |
||||
iconfont: 'mdi' // default - only for display purposes
|
||||
}, |
||||
theme: { |
||||
themes: { |
||||
dark: { |
||||
primary: '#c0c000', |
||||
secondary: '#00c0c0', |
||||
accent: colors.yellow.accent2, |
||||
error: colors.red, |
||||
warning: colors.orange, |
||||
info: colors.lightBlue, |
||||
success: colors.green, |
||||
background: '#121212' |
||||
} |
||||
}, |
||||
options: { |
||||
customProperties: true |
||||
}, |
||||
dark: true |
||||
}, |
||||
lang: { |
||||
t: (key, ...params) => VueI18n.t(key, params).toString() |
||||
} |
||||
}); |
@ -1,252 +0,0 @@
@@ -1,252 +0,0 @@
|
||||
import Vue from 'vue'; |
||||
import VueRouter from 'vue-router'; |
||||
import HomePage from '../views/HomePage.vue'; |
||||
import ChannelsPage from '../views/ChannelsPage.vue'; |
||||
import FFmpegProfilesPage from '../views/FFmpegProfilesPage.vue'; |
||||
import AddEditFFmpegProfilePage from '../views/AddEditFFmpegProfilePage.vue'; |
||||
|
||||
Vue.use(VueRouter); |
||||
|
||||
const routes = [ |
||||
{ |
||||
path: '/', |
||||
name: 'home.title', |
||||
component: HomePage, |
||||
meta: { |
||||
icon: 'mdi-home', |
||||
disabled: false |
||||
} |
||||
}, |
||||
{ |
||||
path: '/channels', |
||||
name: 'channels.title', |
||||
component: ChannelsPage, |
||||
meta: { |
||||
icon: 'mdi-broadcast', |
||||
disabled: false |
||||
} |
||||
}, |
||||
{ |
||||
path: '/ffmpeg-profiles', |
||||
name: 'ffmpeg-profiles.title', |
||||
component: FFmpegProfilesPage, |
||||
meta: { |
||||
icon: 'mdi-video-input-component', |
||||
disabled: false |
||||
}, |
||||
showchildren: false |
||||
}, |
||||
{ |
||||
path: '/watermarks', |
||||
name: 'watermarks.title', |
||||
meta: { |
||||
icon: 'mdi-watermark', |
||||
disabled: true |
||||
} |
||||
}, |
||||
{ |
||||
path: '/sources', |
||||
name: 'media-sources.title', |
||||
meta: { |
||||
icon: 'mdi-server-network', |
||||
disabled: false |
||||
}, |
||||
showchildren: true, |
||||
children: [ |
||||
{ |
||||
path: '/sources/local', |
||||
name: 'media-sources.local.title', |
||||
meta: { |
||||
icon: 'mdi-folder', |
||||
disabled: true |
||||
} |
||||
}, |
||||
{ |
||||
path: '/sources/emby', |
||||
name: 'media-sources.emby.title', |
||||
meta: { |
||||
icon: 'mdi-emby', |
||||
disabled: true |
||||
} |
||||
}, |
||||
{ |
||||
path: '/sources/jellyfin', |
||||
name: 'media-sources.jellyfin.title', |
||||
meta: { |
||||
icon: 'mdi-jellyfish', |
||||
disabled: true |
||||
} |
||||
}, |
||||
{ |
||||
path: '/sources/plex', |
||||
name: 'media-sources.plex.title', |
||||
meta: { |
||||
icon: 'mdi-plex', |
||||
disabled: true |
||||
} |
||||
} |
||||
] |
||||
}, |
||||
{ |
||||
path: '/media', |
||||
name: 'media.title', |
||||
meta: { |
||||
icon: 'mdi-cog', |
||||
disabled: false |
||||
}, |
||||
showchildren: true, |
||||
children: [ |
||||
{ |
||||
path: '/media/libraries', |
||||
name: 'media.libraries.title', |
||||
meta: { |
||||
icon: 'mdi-library', |
||||
disabled: true |
||||
} |
||||
}, |
||||
{ |
||||
path: '/media/trash', |
||||
name: 'media.trash.title', |
||||
meta: { |
||||
icon: 'mdi-trash-can', |
||||
disabled: true |
||||
} |
||||
}, |
||||
{ |
||||
path: '/media/tv-shows', |
||||
name: 'media.tv-shows.title', |
||||
meta: { |
||||
icon: 'mdi-television-classic', |
||||
disabled: true |
||||
} |
||||
}, |
||||
{ |
||||
path: '/media/movies', |
||||
name: 'media.movies.title', |
||||
meta: { |
||||
icon: 'mdi-movie', |
||||
disabled: true |
||||
} |
||||
}, |
||||
{ |
||||
path: '/media/music-videos', |
||||
name: 'media.music-videos.title', |
||||
meta: { |
||||
icon: 'mdi-music-circle', |
||||
disabled: true |
||||
} |
||||
}, |
||||
{ |
||||
path: '/media/other-videos', |
||||
name: 'media.other-videos.title', |
||||
meta: { |
||||
icon: 'mdi-video', |
||||
disabled: true |
||||
} |
||||
}, |
||||
{ |
||||
path: '/media/songs', |
||||
name: 'media.songs.title', |
||||
meta: { |
||||
icon: 'mdi-album', |
||||
disabled: true |
||||
} |
||||
} |
||||
] |
||||
}, |
||||
{ |
||||
path: '/lists', |
||||
name: 'lists.title', |
||||
meta: { |
||||
icon: 'mdi-format-list-bulleted', |
||||
disabled: false |
||||
}, |
||||
showchildren: true, |
||||
children: [ |
||||
{ |
||||
path: '/lists/collections', |
||||
name: 'lists.collections.title', |
||||
meta: { |
||||
icon: 'mdi-collage', |
||||
disabled: true |
||||
} |
||||
}, |
||||
{ |
||||
path: '/lists/trakt-lists', |
||||
name: 'lists.trakt-lists.title', |
||||
meta: { |
||||
icon: 'mdi-hammer', |
||||
disabled: true |
||||
} |
||||
}, |
||||
{ |
||||
path: '/lists/filler-presets', |
||||
name: 'lists.filler-presets.title', |
||||
meta: { |
||||
icon: 'mdi-tune-vertical', |
||||
disabled: true |
||||
} |
||||
} |
||||
] |
||||
}, |
||||
{ |
||||
path: '/schedules', |
||||
name: 'schedules.title', |
||||
meta: { |
||||
icon: 'mdi-calendar', |
||||
disabled: true |
||||
} |
||||
}, |
||||
{ |
||||
path: '/playouts', |
||||
name: 'playouts.title', |
||||
meta: { |
||||
icon: 'mdi-clipboard-play-multiple', |
||||
disabled: true |
||||
} |
||||
}, |
||||
{ |
||||
path: '/settings', |
||||
name: 'settings.title', |
||||
meta: { |
||||
icon: 'mdi-cog', |
||||
disabled: true |
||||
} |
||||
}, |
||||
{ |
||||
path: '/Logs', |
||||
name: 'logs.title', |
||||
meta: { |
||||
icon: 'mdi-card-text', |
||||
disabled: true |
||||
} |
||||
}, |
||||
//hidden routes - used for non-menu routes
|
||||
{ |
||||
path: '/add-ffmpeg-profile', |
||||
name: 'add-ffmpeg-profile', |
||||
component: AddEditFFmpegProfilePage, |
||||
meta: { |
||||
disabled: false, |
||||
hidden: true |
||||
} |
||||
}, |
||||
{ |
||||
path: '/edit-ffmpeg-profile', |
||||
name: 'edit-ffmpeg', |
||||
component: AddEditFFmpegProfilePage, |
||||
meta: { |
||||
disabled: false, |
||||
hidden: true |
||||
}, |
||||
props: (route) => ({ query: route.query.id }) |
||||
} |
||||
]; |
||||
|
||||
const router = new VueRouter({ |
||||
mode: 'history', |
||||
base: process.env.BASE_URL, |
||||
routes |
||||
}); |
||||
|
||||
export default router; |
@ -1,72 +0,0 @@
@@ -1,72 +0,0 @@
|
||||
import type { AxiosError, AxiosInstance, AxiosResponse } from 'axios'; |
||||
import axios from 'axios'; |
||||
|
||||
export function isAxiosError(value: any): value is AxiosError { |
||||
return typeof value?.response === 'object'; |
||||
} |
||||
|
||||
export abstract class AbstractApiService { |
||||
protected readonly http: AxiosInstance; |
||||
|
||||
protected constructor( |
||||
protected readonly path?: string, |
||||
protected readonly baseURL: string = process.env.VUE_APP_ETV_BASE_URL ?? |
||||
'/' |
||||
) { |
||||
if (path) { |
||||
this.baseURL += path; |
||||
} |
||||
this.http = axios.create({ |
||||
baseURL |
||||
// ... further stuff, e.g. `withCredentials: true`
|
||||
}); |
||||
this.http.defaults.headers.common['Accept'] = |
||||
'application/json;charset=UTF-8'; |
||||
this.http.defaults.headers.common['Content-Type'] = |
||||
'application/json;charset=UTF-8'; |
||||
} |
||||
|
||||
protected createParams(record: Record<string, any>): URLSearchParams { |
||||
const params: URLSearchParams = new URLSearchParams(); |
||||
for (const key in record) { |
||||
if (Object.prototype.hasOwnProperty.call(record, key)) { |
||||
const value: any = record[key]; |
||||
if (value !== null && value !== undefined) { |
||||
params.append(key, value); |
||||
} else { |
||||
console.debug( |
||||
`Param key '${key}' was null or undefined and will be ignored` |
||||
); |
||||
} |
||||
} |
||||
} |
||||
return params; |
||||
} |
||||
|
||||
protected handleResponse<T>(response: AxiosResponse<T>): T { |
||||
return response.data; |
||||
} |
||||
|
||||
protected handleError(error: unknown): never { |
||||
if (error instanceof Error) { |
||||
if (isAxiosError(error)) { |
||||
if (error.response) { |
||||
console.log(error.response.data); |
||||
console.log(error.response.status); |
||||
console.log(error.response.headers); |
||||
throw error; |
||||
} else if (error.request) { |
||||
// The request was made but no response was received
|
||||
// `error.request` is an instance of XMLHttpRequest in the browser
|
||||
console.log(error.request); |
||||
throw new Error(error as any); |
||||
} |
||||
} else { |
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.log('Error', error.message); |
||||
throw new Error(error.message); |
||||
} |
||||
} |
||||
throw new Error(error as any); |
||||
} |
||||
} |
@ -1,17 +0,0 @@
@@ -1,17 +0,0 @@
|
||||
import { AbstractApiService } from './AbstractApiService'; |
||||
import { Channel } from '@/models/Channel'; |
||||
|
||||
class ChannelApiService extends AbstractApiService { |
||||
public constructor() { |
||||
super(); |
||||
} |
||||
|
||||
public getAll(): Promise<Channel[]> { |
||||
return this.http |
||||
.get('/api/channels') |
||||
.then(this.handleResponse.bind(this)) |
||||
.catch(this.handleError.bind(this)); |
||||
} |
||||
} |
||||
|
||||
export const channelApiService: ChannelApiService = new ChannelApiService(); |
@ -1,114 +0,0 @@
@@ -1,114 +0,0 @@
|
||||
import { AbstractApiService } from './AbstractApiService'; |
||||
import { FFmpegProfile } from '@/models/FFmpegProfile'; |
||||
import { FFmpegFullProfile } from '../models/FFmpegFullProfile'; |
||||
|
||||
class FFmpegProfileApiService extends AbstractApiService { |
||||
public constructor() { |
||||
super(); |
||||
} |
||||
|
||||
public getAll(): Promise<FFmpegProfile[]> { |
||||
return this.http |
||||
.get('/api/ffmpeg/profiles') |
||||
.then(this.handleResponse.bind(this)) |
||||
.catch(this.handleError.bind(this)); |
||||
} |
||||
|
||||
public getOne(id: string): Promise<FFmpegFullProfile[]> { |
||||
return this.http |
||||
.get('/api/ffmpeg/profiles/' + id) |
||||
.then(this.handleResponse.bind(this)) |
||||
.catch(this.handleError.bind(this)); |
||||
} |
||||
|
||||
public newFFmpegProfile( |
||||
Name: string, |
||||
ThreadCount: number, |
||||
HardwareAcceleration: number, |
||||
VaapiDriver: number, |
||||
VaapiDevice: string, |
||||
ResolutionId: number, |
||||
VideoFormat: number, |
||||
VideoBitrate: number, |
||||
VideoBufferSize: number, |
||||
AudioFormat: number, |
||||
AudioBitrate: number, |
||||
AudioBufferSize: number, |
||||
NormalizeLoudness: boolean, |
||||
AudioChannels: number, |
||||
AudioSampleRate: number, |
||||
NormalizeFramerate: boolean, |
||||
DeinterlaceVideo: boolean |
||||
) { |
||||
const data = { |
||||
Name: Name, |
||||
ThreadCount: ThreadCount, |
||||
HardwareAcceleration: HardwareAcceleration, |
||||
VaapiDriver: VaapiDriver, |
||||
VaapiDevice: VaapiDevice, |
||||
ResolutionId: ResolutionId, |
||||
VideoFormat: VideoFormat, |
||||
VideoBitrate: VideoBitrate, |
||||
VideoBufferSize: VideoBufferSize, |
||||
AudioFormat: AudioFormat, |
||||
AudioBitrate: AudioBitrate, |
||||
AudioBufferSize: AudioBufferSize, |
||||
NormalizeLoudness: NormalizeLoudness, |
||||
AudioChannels: AudioChannels, |
||||
AudioSampleRate: AudioSampleRate, |
||||
NormalizeFramerate: NormalizeFramerate, |
||||
DeinterlaceVideo: DeinterlaceVideo |
||||
}; |
||||
this.http.post('/api/ffmpeg/profiles/new', data); |
||||
} |
||||
|
||||
public updateFFmpegProfile( |
||||
Id: number, |
||||
Name: string, |
||||
ThreadCount: number, |
||||
HardwareAcceleration: number, |
||||
VaapiDriver: number, |
||||
VaapiDevice: string, |
||||
ResolutionId: number, |
||||
VideoFormat: number, |
||||
VideoBitrate: number, |
||||
VideoBufferSize: number, |
||||
AudioFormat: number, |
||||
AudioBitrate: number, |
||||
AudioBufferSize: number, |
||||
NormalizeLoudness: boolean, |
||||
AudioChannels: number, |
||||
AudioSampleRate: number, |
||||
NormalizeFramerate: boolean, |
||||
DeinterlaceVideo: boolean |
||||
) { |
||||
const data = { |
||||
Id: Id, |
||||
Name: Name, |
||||
ThreadCount: ThreadCount, |
||||
HardwareAcceleration: HardwareAcceleration, |
||||
VaapiDriver: VaapiDriver, |
||||
VaapiDevice: VaapiDevice, |
||||
ResolutionId: ResolutionId, |
||||
VideoFormat: VideoFormat, |
||||
VideoBitrate: VideoBitrate, |
||||
VideoBufferSize: VideoBufferSize, |
||||
AudioFormat: AudioFormat, |
||||
AudioBitrate: AudioBitrate, |
||||
AudioBufferSize: AudioBufferSize, |
||||
NormalizeLoudness: NormalizeLoudness, |
||||
AudioChannels: AudioChannels, |
||||
AudioSampleRate: AudioSampleRate, |
||||
NormalizeFramerate: NormalizeFramerate, |
||||
DeinterlaceVideo: DeinterlaceVideo |
||||
}; |
||||
this.http.put('/api/ffmpeg/profiles/update', data); |
||||
} |
||||
|
||||
public deleteRecord(id: string) { |
||||
this.http.delete('/api/ffmpeg/delete/' + id); |
||||
} |
||||
} |
||||
|
||||
export const ffmpegProfileApiService: FFmpegProfileApiService = |
||||
new FFmpegProfileApiService(); |
@ -1,16 +0,0 @@
@@ -1,16 +0,0 @@
|
||||
import { AbstractApiService } from './AbstractApiService'; |
||||
|
||||
class VersionApiService extends AbstractApiService { |
||||
public constructor() { |
||||
super(); |
||||
} |
||||
|
||||
public version(): Promise<string> { |
||||
return this.http |
||||
.get('/api/version') |
||||
.then(this.handleResponse.bind(this)) |
||||
.catch(this.handleError.bind(this)); |
||||
} |
||||
} |
||||
|
||||
export const versionApiService: VersionApiService = new VersionApiService(); |
@ -1,11 +0,0 @@
@@ -1,11 +0,0 @@
|
||||
import Vue, { VNode } from 'vue'; |
||||
|
||||
declare global { |
||||
namespace JSX { |
||||
interface Element extends VNode {} |
||||
interface ElementClass extends Vue {} |
||||
interface IntrinsicElements { |
||||
[elem: string]: any; |
||||
} |
||||
} |
||||
} |
@ -1,4 +0,0 @@
@@ -1,4 +0,0 @@
|
||||
declare module '*.vue' { |
||||
import Vue from 'vue'; |
||||
export default Vue; |
||||
} |
@ -1,46 +0,0 @@
@@ -1,46 +0,0 @@
|
||||
import { defineStore } from 'pinia'; |
||||
import { versionApiService } from '@/services/VersionService'; |
||||
|
||||
const originURL = `${window.location.origin}`; |
||||
|
||||
export const applicationState = defineStore('appState', { |
||||
state: () => { |
||||
return { |
||||
miniMenu: false, |
||||
currentVersion: 'unknown', |
||||
m3uURL: originURL + '/iptv/channels.m3u', // this will need to be fixed for reverse proxies
|
||||
xmlURL: originURL + '/iptv/xmltv.xml', // this will need to be fixed for reverse proxies
|
||||
documentationURL: 'https://ersatztv.org/', |
||||
githubURL: 'https://github.com/ErsatzTV/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; |
||||
}, |
||||
async getVersion() { |
||||
this.currentVersion = await versionApiService.version(); |
||||
} |
||||
} |
||||
}); |
@ -1,28 +0,0 @@
@@ -1,28 +0,0 @@
|
||||
import { defineStore } from 'pinia'; |
||||
import VueI18n from '../plugins/i18n'; |
||||
|
||||
export const languageState = defineStore('languageState', { |
||||
state: () => { |
||||
return { |
||||
language: 'en' |
||||
}; |
||||
}, |
||||
getters: { |
||||
currentLanguageCode: (state) => { |
||||
return state.language; |
||||
} |
||||
}, |
||||
actions: { |
||||
setLanguage(languageCode) { |
||||
this.language = languageCode; |
||||
VueI18n.locale = languageCode; |
||||
} |
||||
}, |
||||
persist: { |
||||
afterRestore: (context) => { |
||||
if (context.store.language) { |
||||
VueI18n.locale = context.store.language; |
||||
} |
||||
} |
||||
} |
||||
}); |
@ -1,27 +0,0 @@
@@ -1,27 +0,0 @@
|
||||
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; |
||||
} |
||||
} |
||||
}); |
@ -1,909 +0,0 @@
@@ -1,909 +0,0 @@
|
||||
<template> |
||||
<div id="AddEditFFmpegProfile"> |
||||
<h1 id="title" class="mx-4" /> |
||||
<v-divider color="success" class="ma-2"></v-divider> |
||||
<v-app> |
||||
<v-form |
||||
ref="form" |
||||
v-model="isFormValid" |
||||
id="ffmpegForm" |
||||
lazy-validation |
||||
> |
||||
<v-container> |
||||
<v-row justify="center"> |
||||
<v-flex shrink class="pb-10"> |
||||
<v-container style="max-height: 80px"> |
||||
<v-row justify="center"> |
||||
<h2 class="mx"> |
||||
{{ $t('edit-ffmpeg-profile.General') }} |
||||
</h2> |
||||
</v-row> |
||||
</v-container> |
||||
<v-container style="max-height: 80px"> |
||||
<v-text-field |
||||
v-model="newProfile.name" |
||||
:rules="textRules" |
||||
:label="$t('edit-ffmpeg-profile.Name')" |
||||
required |
||||
></v-text-field> |
||||
</v-container> |
||||
<v-container |
||||
class="d-flex" |
||||
style="max-height: 80px" |
||||
> |
||||
<v-text-field |
||||
ref="myThreadCount" |
||||
v-model="threadCount" |
||||
:label=" |
||||
$t('edit-ffmpeg-profile.thread-count') |
||||
" |
||||
:rules="validInt" |
||||
number |
||||
required |
||||
></v-text-field> |
||||
<v-tooltip v-model="threadCountShow" top> |
||||
<template v-slot:activator="{ on, attrs }"> |
||||
<v-icon |
||||
class="pl-2" |
||||
color="grey" |
||||
v-ripple="false" |
||||
:retain-focus="false" |
||||
v-bind="attrs" |
||||
v-on="on" |
||||
> |
||||
mdi-help-circle-outline |
||||
</v-icon> |
||||
</template> |
||||
<span>Recommended Thread Count: 0</span> |
||||
</v-tooltip> |
||||
</v-container> |
||||
<v-container style="max-height: 40px"> |
||||
<v-select |
||||
v-model="selectedResolution" |
||||
@change="preferredResolutionChange" |
||||
:items="preferredResolutions" |
||||
item-value="id" |
||||
item-text="name" |
||||
:label=" |
||||
$t( |
||||
'edit-ffmpeg-profile.preferred-resolution' |
||||
) |
||||
" |
||||
required |
||||
></v-select> |
||||
</v-container> |
||||
</v-flex> |
||||
<v-divider |
||||
style="max-height: 500px" |
||||
vertical |
||||
color="grey" |
||||
></v-divider> |
||||
<v-flex shrink class="pb-10"> |
||||
<v-container style="max-height: 80px"> |
||||
<v-row justify="center"> |
||||
<h2 class="mx"> |
||||
{{ $t('edit-ffmpeg-profile.Video') }} |
||||
</h2> |
||||
</v-row> |
||||
</v-container> |
||||
<v-container |
||||
class="d-flex" |
||||
style="max-height: 80px" |
||||
> |
||||
<v-select |
||||
v-model="selectedVideoFormat" |
||||
@change="videoFormatChanged" |
||||
:items="videoFormats" |
||||
item-value="id" |
||||
item-text="name" |
||||
id="videoFormatSelector" |
||||
:label="$t('edit-ffmpeg-profile.format')" |
||||
required |
||||
></v-select> |
||||
<v-tooltip v-model="videoFormatShow" top> |
||||
<template v-slot:activator="{ on, attrs }"> |
||||
<v-icon |
||||
class="pl-2" |
||||
color="grey" |
||||
v-ripple="false" |
||||
:retain-focus="false" |
||||
v-bind="attrs" |
||||
v-on="on" |
||||
> |
||||
mdi-help-circle-outline |
||||
</v-icon> |
||||
</template> |
||||
<span>Recommended Thread Count: 0</span> |
||||
</v-tooltip> |
||||
</v-container> |
||||
<v-container |
||||
class="d-flex" |
||||
style="max-height: 80px" |
||||
> |
||||
<v-text-field |
||||
v-model="newProfile.videoBitRate" |
||||
:label="$t('edit-ffmpeg-profile.bitrate')" |
||||
:rules="validIntNonZero" |
||||
required |
||||
></v-text-field> |
||||
<v-tooltip v-model="videoBitRateShow" top> |
||||
<template v-slot:activator="{ on, attrs }"> |
||||
<v-icon |
||||
class="pl-2" |
||||
color="grey" |
||||
v-ripple="false" |
||||
:retain-focus="false" |
||||
v-bind="attrs" |
||||
v-on="on" |
||||
> |
||||
mdi-help-circle-outline |
||||
</v-icon> |
||||
</template> |
||||
<span>Recommended Thread Count: 0</span> |
||||
</v-tooltip> |
||||
</v-container> |
||||
<v-container |
||||
class="d-flex" |
||||
style="max-height: 80px" |
||||
> |
||||
<v-text-field |
||||
v-model="newProfile.videoBufferSize" |
||||
:label=" |
||||
$t('edit-ffmpeg-profile.buffer-size') |
||||
" |
||||
:rules="validIntNonZero" |
||||
required |
||||
></v-text-field> |
||||
<v-tooltip v-model="videoBufferSizeShow" top> |
||||
<template v-slot:activator="{ on, attrs }"> |
||||
<v-icon |
||||
class="pl-2" |
||||
color="grey" |
||||
v-ripple="false" |
||||
:retain-focus="false" |
||||
v-bind="attrs" |
||||
v-on="on" |
||||
> |
||||
mdi-help-circle-outline |
||||
</v-icon> |
||||
</template> |
||||
<span>Recommended Thread Count: 0</span> |
||||
</v-tooltip> |
||||
</v-container> |
||||
<v-container |
||||
class="d-flex" |
||||
style="max-height: 80px" |
||||
> |
||||
<v-select |
||||
v-model="selectedHardwareAcceleration" |
||||
@change="hardwareAccelerationChanged" |
||||
:items="hardwareAccelerations" |
||||
item-value="id" |
||||
item-text="name" |
||||
:label=" |
||||
$t( |
||||
'edit-ffmpeg-profile.hardware-acceleration' |
||||
) |
||||
" |
||||
required |
||||
></v-select> |
||||
<v-tooltip |
||||
v-model="hardwareAccelerationShow" |
||||
top |
||||
> |
||||
<template v-slot:activator="{ on, attrs }"> |
||||
<v-icon |
||||
class="pl-2" |
||||
color="grey" |
||||
v-ripple="false" |
||||
:retain-focus="false" |
||||
v-bind="attrs" |
||||
v-on="on" |
||||
> |
||||
mdi-help-circle-outline |
||||
</v-icon> |
||||
</template> |
||||
<span>Recommended Thread Count: 0</span> |
||||
</v-tooltip> |
||||
</v-container> |
||||
<v-container |
||||
class="d-flex" |
||||
style="max-height: 80px" |
||||
> |
||||
<v-select |
||||
v-model="selectedVaapiDriver" |
||||
@change="vaapiDriverChanged" |
||||
:items="vaapiDrivers" |
||||
:disabled="vaapiDriverDisabled" |
||||
item-value="id" |
||||
item-text="name" |
||||
:label=" |
||||
$t('edit-ffmpeg-profile.vaapi-driver') |
||||
" |
||||
required |
||||
></v-select> |
||||
<v-tooltip v-model="vaapiDriverShow" top> |
||||
<template v-slot:activator="{ on, attrs }"> |
||||
<v-icon |
||||
class="pl-2" |
||||
color="grey" |
||||
v-ripple="false" |
||||
:retain-focus="false" |
||||
v-bind="attrs" |
||||
v-on="on" |
||||
> |
||||
mdi-help-circle-outline |
||||
</v-icon> |
||||
</template> |
||||
<span>Recommended Thread Count: 0</span> |
||||
</v-tooltip> |
||||
</v-container> |
||||
<v-container |
||||
class="d-flex" |
||||
style="max-height: 80px" |
||||
> |
||||
<v-select |
||||
v-model="selectedVaapiDevice" |
||||
@change="vaapiDeviceChanged" |
||||
:items="vaapiDevices" |
||||
:disabled="vaapiDriverDisabled" |
||||
:label=" |
||||
$t('edit-ffmpeg-profile.vaapi-device') |
||||
" |
||||
required |
||||
></v-select> |
||||
<v-tooltip v-model="vaapiDeviceShow" top> |
||||
<template v-slot:activator="{ on, attrs }"> |
||||
<v-icon |
||||
class="pl-2" |
||||
color="grey" |
||||
v-ripple="false" |
||||
:retain-focus="false" |
||||
v-bind="attrs" |
||||
v-on="on" |
||||
> |
||||
mdi-help-circle-outline |
||||
</v-icon> |
||||
</template> |
||||
<span>Recommended Thread Count: 0</span> |
||||
</v-tooltip> |
||||
</v-container> |
||||
<v-container |
||||
class="d-flex" |
||||
style="max-height: 50px" |
||||
> |
||||
<v-checkbox |
||||
class="mr-2" |
||||
v-model="normalizeFrameRate" |
||||
:label=" |
||||
$t( |
||||
'edit-ffmpeg-profile.normalize-framerate' |
||||
) |
||||
" |
||||
color="green lighten-1" |
||||
required |
||||
></v-checkbox> |
||||
<v-tooltip v-model="normalizeFrameRateShow" top> |
||||
<template v-slot:activator="{ on, attrs }"> |
||||
<v-icon |
||||
class="pt-8" |
||||
color="grey" |
||||
v-ripple="false" |
||||
:retain-focus="false" |
||||
v-bind="attrs" |
||||
v-on="on" |
||||
> |
||||
mdi-help-circle-outline |
||||
</v-icon> |
||||
</template> |
||||
<span>Recommended Thread Count: 0</span> |
||||
</v-tooltip> |
||||
</v-container> |
||||
<v-container |
||||
class="d-flex" |
||||
style="max-height: 50px" |
||||
> |
||||
<v-checkbox |
||||
class="mr-2" |
||||
v-model="autoDeinterlaceVideo" |
||||
:label=" |
||||
$t( |
||||
'edit-ffmpeg-profile.auto-deinterlace-video' |
||||
) |
||||
" |
||||
color="green lighten-1" |
||||
required |
||||
></v-checkbox> |
||||
<v-tooltip |
||||
v-model="autoDeinterlaceVideoShow" |
||||
top |
||||
> |
||||
<template v-slot:activator="{ on, attrs }"> |
||||
<v-icon |
||||
class="pt-8" |
||||
color="grey" |
||||
v-ripple="false" |
||||
:retain-focus="false" |
||||
v-bind="attrs" |
||||
v-on="on" |
||||
> |
||||
mdi-help-circle-outline |
||||
</v-icon> |
||||
</template> |
||||
<span>Recommended Thread Count: 0</span> |
||||
</v-tooltip> |
||||
</v-container> |
||||
</v-flex> |
||||
<v-divider |
||||
style="max-height: 500px" |
||||
vertical |
||||
color="grey" |
||||
></v-divider> |
||||
<v-flex shrink class="pb-10"> |
||||
<v-container style="max-height: 80px"> |
||||
<v-row justify="center"> |
||||
<h2 class="mx"> |
||||
{{ $t('edit-ffmpeg-profile.Audio') }} |
||||
</h2> |
||||
</v-row> |
||||
</v-container> |
||||
<v-container |
||||
class="d-flex" |
||||
style="max-height: 80px" |
||||
> |
||||
<v-select |
||||
v-model="selectedAudioFormat" |
||||
@change="audioFormatChanged" |
||||
:items="audioFormats" |
||||
item-value="id" |
||||
item-text="name" |
||||
:label="$t('edit-ffmpeg-profile.format')" |
||||
ref="audioFormat" |
||||
required |
||||
></v-select> |
||||
<v-tooltip v-model="audioFormatShow" top> |
||||
<template v-slot:activator="{ on, attrs }"> |
||||
<v-icon |
||||
class="pl-2" |
||||
color="grey" |
||||
v-ripple="false" |
||||
:retain-focus="false" |
||||
v-bind="attrs" |
||||
v-on="on" |
||||
> |
||||
mdi-help-circle-outline |
||||
</v-icon> |
||||
</template> |
||||
<span>Recommended Thread Count: 0</span> |
||||
</v-tooltip> |
||||
</v-container> |
||||
<v-container |
||||
class="d-flex" |
||||
style="max-height: 80px" |
||||
> |
||||
<v-text-field |
||||
v-model="newProfile.audioBitRate" |
||||
:label="$t('edit-ffmpeg-profile.bitrate')" |
||||
:rules="validIntNonZero" |
||||
required |
||||
></v-text-field> |
||||
<v-tooltip v-model="audioBitRateShow" top> |
||||
<template v-slot:activator="{ on, attrs }"> |
||||
<v-icon |
||||
class="pl-2" |
||||
color="grey" |
||||
v-ripple="false" |
||||
:retain-focus="false" |
||||
v-bind="attrs" |
||||
v-on="on" |
||||
> |
||||
mdi-help-circle-outline |
||||
</v-icon> |
||||
</template> |
||||
<span>Recommended Thread Count: 0</span> |
||||
</v-tooltip> |
||||
</v-container> |
||||
<v-container |
||||
class="d-flex" |
||||
style="max-height: 80px" |
||||
> |
||||
<v-text-field |
||||
v-model="newProfile.audioBufferSize" |
||||
:label=" |
||||
$t('edit-ffmpeg-profile.buffer-size') |
||||
" |
||||
:rules="validIntNonZero" |
||||
required |
||||
></v-text-field> |
||||
<v-tooltip v-model="audioBufferSizeShow" top> |
||||
<template v-slot:activator="{ on, attrs }"> |
||||
<v-icon |
||||
class="pl-2" |
||||
color="grey" |
||||
v-ripple="false" |
||||
:retain-focus="false" |
||||
v-bind="attrs" |
||||
v-on="on" |
||||
> |
||||
mdi-help-circle-outline |
||||
</v-icon> |
||||
</template> |
||||
<span>Recommended Thread Count: 0</span> |
||||
</v-tooltip> |
||||
</v-container> |
||||
<v-container |
||||
class="d-flex" |
||||
style="max-height: 80px" |
||||
> |
||||
<v-text-field |
||||
v-model="newProfile.channels" |
||||
:label="$t('edit-ffmpeg-profile.channels')" |
||||
:rules="validIntNonZero" |
||||
required |
||||
></v-text-field> |
||||
<v-tooltip v-model="audioChannelsShow" top> |
||||
<template v-slot:activator="{ on, attrs }"> |
||||
<v-icon |
||||
class="pl-2" |
||||
color="grey" |
||||
v-ripple="false" |
||||
:retain-focus="false" |
||||
v-bind="attrs" |
||||
v-on="on" |
||||
> |
||||
mdi-help-circle-outline |
||||
</v-icon> |
||||
</template> |
||||
<span>Recommended Thread Count: 0</span> |
||||
</v-tooltip> |
||||
</v-container> |
||||
<v-container |
||||
class="d-flex" |
||||
style="max-height: 80px" |
||||
> |
||||
<v-text-field |
||||
v-model="newProfile.audioSampleRate" |
||||
:label=" |
||||
$t('edit-ffmpeg-profile.sample-rate') |
||||
" |
||||
:rules="validIntNonZero" |
||||
required |
||||
></v-text-field> |
||||
<v-tooltip v-model="audioSampleRateShow" top> |
||||
<template v-slot:activator="{ on, attrs }"> |
||||
<v-icon |
||||
class="pl-2" |
||||
color="grey" |
||||
v-ripple="false" |
||||
:retain-focus="false" |
||||
v-bind="attrs" |
||||
v-on="on" |
||||
> |
||||
mdi-help-circle-outline |
||||
</v-icon> |
||||
</template> |
||||
<span>Recommended Thread Count: 0</span> |
||||
</v-tooltip> |
||||
</v-container> |
||||
<v-container |
||||
class="d-flex" |
||||
style="max-height: 80px" |
||||
> |
||||
<v-checkbox |
||||
v-model="normalizeLoudness" |
||||
:label=" |
||||
$t( |
||||
'edit-ffmpeg-profile.normalize-loudness' |
||||
) |
||||
" |
||||
color="green lighten-1" |
||||
required |
||||
></v-checkbox> |
||||
<v-tooltip v-model="normalizeLoudnessShow" top> |
||||
<template v-slot:activator="{ on, attrs }"> |
||||
<v-icon |
||||
class="pl-2" |
||||
color="grey" |
||||
v-ripple="false" |
||||
:retain-focus="false" |
||||
v-bind="attrs" |
||||
v-on="on" |
||||
> |
||||
mdi-help-circle-outline |
||||
</v-icon> |
||||
</template> |
||||
<span>Recommended Thread Count: 0</span> |
||||
</v-tooltip> |
||||
</v-container> |
||||
<v-spacer style="height: 80px"></v-spacer> |
||||
<v-container> |
||||
<v-btn |
||||
color="green lighten-1" |
||||
class="ma-2" |
||||
:disabled="!isFormValid" |
||||
@click="saveFFmpegProfile()" |
||||
> |
||||
{{ $t('edit-ffmpeg-profile.save-profile') }} |
||||
</v-btn> |
||||
<v-btn |
||||
color="cancel" |
||||
class="ma-2" |
||||
@click="cancelAdd()" |
||||
> |
||||
{{ $t('edit-ffmpeg-profile.cancel') }} |
||||
</v-btn> |
||||
<v-btn |
||||
color="indigo accent-1" |
||||
class="ma-2" |
||||
@click="cancelAdd()" |
||||
> |
||||
{{ $t('edit-ffmpeg-profile.help') }} |
||||
</v-btn> |
||||
</v-container> |
||||
</v-flex> |
||||
</v-row> |
||||
</v-container> |
||||
</v-form> |
||||
</v-app> |
||||
</div> |
||||
</template> |
||||
|
||||
<script lang="ts"> |
||||
import { Vue, Component, Watch } from 'vue-property-decorator'; |
||||
import { ffmpegProfileApiService } from '@/services/FFmpegProfileService'; |
||||
|
||||
@Component |
||||
export default class AddEditFFmpegProfile extends Vue { |
||||
//@Name({ default: 'AddEditFFmpegProfile' }) AddEditFFmpegProfile!: string; |
||||
//@Prop({ default: -1 }) private id!: number; |
||||
public newProfile: any = {}; |
||||
private refForm: any = this.$refs.form; |
||||
public isFormValid = false; |
||||
public threadCountShow = false; |
||||
public videoFormatShow = false; |
||||
public videoBitRateShow = false; |
||||
public videoBufferSizeShow = false; |
||||
public hardwareAccelerationShow = false; |
||||
public vaapiDriverShow = false; |
||||
public vaapiDeviceShow = false; |
||||
public normalizeFrameRateShow = false; |
||||
public autoDeinterlaceVideoShow = false; |
||||
public audioFormatShow = false; |
||||
public audioBitRateShow = false; |
||||
public audioBufferSizeShow = false; |
||||
public audioChannelsShow = false; |
||||
public audioSampleRateShow = false; |
||||
public normalizeLoudnessShow = false; |
||||
|
||||
public AddEditFFmpegProfile() { |
||||
console.log('test'); |
||||
} |
||||
|
||||
public audioFormats: [ |
||||
{ id: number; name: string }, |
||||
{ id: number; name: string } |
||||
] = [ |
||||
{ id: 1, name: 'aac' }, |
||||
{ id: 2, name: 'ac3' } |
||||
]; |
||||
|
||||
private _selectedAudioFormat: number = 2; |
||||
public selectedAudioFormat: { id: number; name: string } = { |
||||
id: 2, |
||||
name: 'ac3' |
||||
}; |
||||
|
||||
public preferredResolutions: [ |
||||
{ id: number; name: string }, |
||||
{ id: number; name: string }, |
||||
{ id: number; name: string }, |
||||
{ id: number; name: string } |
||||
] = [ |
||||
{ id: 0, name: '720x480' }, |
||||
{ id: 1, name: '1280x720' }, |
||||
{ id: 2, name: '1920x1080' }, |
||||
{ id: 3, name: '3840x2160' } |
||||
]; |
||||
|
||||
private _selectedResolution: number = 2; |
||||
public selectedResolution: { id: number; name: string } = { |
||||
id: 2, |
||||
name: '1920x1080' |
||||
}; |
||||
|
||||
public videoFormats: [ |
||||
{ id: number; name: string }, |
||||
{ id: number; name: string }, |
||||
{ id: number; name: string } |
||||
] = [ |
||||
{ id: 1, name: 'h264' }, |
||||
{ id: 2, name: 'hevc' }, |
||||
{ id: 3, name: 'mpeg-2' } |
||||
]; |
||||
|
||||
private _selectedVideoFormat: number = 1; |
||||
public selectedVideoFormat: { id: number; name: string } = { |
||||
id: 1, |
||||
name: 'h264' |
||||
}; |
||||
|
||||
public hardwareAccelerations: [ |
||||
{ id: number; name: string }, |
||||
{ id: number; name: string }, |
||||
{ id: number; name: string }, |
||||
{ id: number; name: string }, |
||||
{ id: number; name: string } |
||||
] = [ |
||||
{ id: 0, name: 'None' }, |
||||
{ id: 1, name: 'Qsv' }, |
||||
{ id: 2, name: 'Nvenc' }, |
||||
{ id: 3, name: 'Vaapi' }, |
||||
{ id: 4, name: 'VideoToolbox' } |
||||
]; |
||||
|
||||
private _selectedHardwareAcceleration: number = 0; |
||||
public selectedHardwareAcceleration: { id: number; name: string } = { |
||||
id: 0, |
||||
name: 'None' |
||||
}; |
||||
|
||||
public vaapiDrivers: [ |
||||
{ id: number; name: string }, |
||||
{ id: number; name: string }, |
||||
{ id: number; name: string }, |
||||
{ id: number; name: string }, |
||||
{ id: number; name: string } |
||||
] = [ |
||||
{ id: 0, name: 'Default' }, |
||||
{ id: 1, name: 'iHD' }, |
||||
{ id: 2, name: 'i965' }, |
||||
{ id: 3, name: 'RadeonSI' }, |
||||
{ id: 4, name: 'Nouveau' } |
||||
]; |
||||
|
||||
public vaapiDriverDisabled = true; |
||||
private _selectedVaapiDriver: number = 0; |
||||
public selectedVaapiDriver: { id: number; name: string } = { |
||||
id: 0, |
||||
name: 'Default' |
||||
}; |
||||
|
||||
public selectedVaapiDevice: string = ''; |
||||
public vaapiDevices: [string, string] = ['', '/dev/dri/renderD128']; |
||||
|
||||
public normalizeFrameRate: boolean = false; |
||||
public autoDeinterlaceVideo: boolean = true; |
||||
public normalizeLoudness: boolean = true; |
||||
|
||||
saveFFmpegProfile() { |
||||
//this means we're adding |
||||
if (isNaN(this.id)) { |
||||
ffmpegProfileApiService.newFFmpegProfile( |
||||
this.newProfile.name, |
||||
this.threadCount, |
||||
this._selectedHardwareAcceleration, |
||||
this._selectedVaapiDriver, |
||||
this.selectedVaapiDevice, |
||||
this._selectedResolution, |
||||
this._selectedVideoFormat, |
||||
this.newProfile.videoBitRate, |
||||
this.newProfile.videoBufferSize, |
||||
this._selectedAudioFormat, |
||||
this.newProfile.audioBitRate, |
||||
this.newProfile.audioBufferSize, |
||||
this.normalizeLoudness, |
||||
this.newProfile.channels, |
||||
this.newProfile.audioSampleRate, |
||||
this.normalizeFrameRate, |
||||
this.autoDeinterlaceVideo |
||||
); |
||||
} else { |
||||
//this means we're editing |
||||
ffmpegProfileApiService.updateFFmpegProfile( |
||||
this.id, |
||||
this.newProfile.name, |
||||
this.threadCount, |
||||
this._selectedHardwareAcceleration, |
||||
this._selectedVaapiDriver, |
||||
this.selectedVaapiDevice, |
||||
this._selectedResolution, |
||||
this._selectedVideoFormat, |
||||
this.newProfile.videoBitRate, |
||||
this.newProfile.videoBufferSize, |
||||
this._selectedAudioFormat, |
||||
this.newProfile.audioBitRate, |
||||
this.newProfile.audioBufferSize, |
||||
this.normalizeLoudness, |
||||
this.newProfile.channels, |
||||
this.newProfile.audioSampleRate, |
||||
this.normalizeFrameRate, |
||||
this.autoDeinterlaceVideo |
||||
); |
||||
} |
||||
const Toast = this.$swal.mixin({ |
||||
toast: true, |
||||
position: 'top-end', |
||||
showConfirmButton: false, |
||||
timer: 3000, |
||||
timerProgressBar: true, |
||||
didOpen: (toast) => { |
||||
toast.addEventListener('mouseenter', this.$swal.stopTimer); |
||||
toast.addEventListener('mouseleave', this.$swal.resumeTimer); |
||||
} |
||||
}); |
||||
|
||||
Toast.fire({ |
||||
icon: 'success', |
||||
title: this.$t('edit-ffmpeg-profile.profile-saved').toString() |
||||
}); |
||||
this.$router.push({ |
||||
name: 'ffmpeg-profiles.title' |
||||
}); |
||||
} |
||||
|
||||
cancelAdd() { |
||||
this.$router.push({ |
||||
name: 'ffmpeg-profiles.title' |
||||
}); |
||||
} |
||||
|
||||
//~change events~// |
||||
public audioFormatChanged(selectObj: number) { |
||||
this._selectedAudioFormat = selectObj; |
||||
} |
||||
|
||||
public preferredResolutionChange(selectObj: number) { |
||||
this._selectedResolution = selectObj + 1; |
||||
} |
||||
|
||||
public videoFormatChanged(selectObj: number) { |
||||
this._selectedVideoFormat = selectObj; |
||||
} |
||||
|
||||
public hardwareAccelerationChanged(selectObj: number) { |
||||
this._selectedHardwareAcceleration = selectObj; |
||||
this.applyVaapiValidation(); |
||||
} |
||||
|
||||
public applyVaapiValidation() { |
||||
//If they pick VAAPI as the hardware acceleration, |
||||
//they can now choose a vaapi driver and device. |
||||
//If not, they cannot change the default options. |
||||
if (this._selectedHardwareAcceleration == 3) { |
||||
this.vaapiDriverDisabled = false; |
||||
} else { |
||||
this.vaapiDriverDisabled = true; |
||||
this._selectedVaapiDriver = 0; |
||||
this.selectedVaapiDriver = { id: 0, name: 'Default' }; |
||||
this.selectedVaapiDevice = ''; |
||||
} |
||||
} |
||||
|
||||
public vaapiDriverChanged(selectObj: number) { |
||||
this._selectedVaapiDriver = selectObj; |
||||
} |
||||
|
||||
public vaapiDeviceChanged(selectObj: string) { |
||||
this.selectedVaapiDevice = selectObj; |
||||
} |
||||
|
||||
//~ end change events~// |
||||
|
||||
public threadCount = 0; |
||||
|
||||
get validIntNonZero() { |
||||
return [ |
||||
(v: any) => |
||||
(v && /^[0-9]+$/.test(v)) || this.$t('Must be a valid number.'), |
||||
(v: any) => (v && v > 0) || 'Must be greater than 0.' |
||||
]; |
||||
} |
||||
|
||||
get validInt() { |
||||
return [ |
||||
(v: any) => |
||||
(v && /^[0-9]+$/.test(v)) || this.$t('Must be a valid number.') |
||||
]; |
||||
} |
||||
|
||||
get textRules() { |
||||
return [(v: any) => (v && v.length > 0) || 'Value must not be empty.']; |
||||
} |
||||
|
||||
props!: { id: number }; |
||||
@Watch('id', { immediate: true }) async onItemChanged() { |
||||
console.log('ID', this.id); |
||||
this.id = Number(this.$route.query.id) ?? -1; |
||||
await this.loadPage(); |
||||
} |
||||
|
||||
private loaded = false; |
||||
private id = -1; |
||||
|
||||
title: string = 'Modify FFmpeg Profile'; |
||||
|
||||
async loadPage(): Promise<void> { |
||||
if (this.loaded) { |
||||
return; |
||||
} |
||||
var title = document.getElementById('title'); |
||||
if (title === null || title === undefined) { |
||||
//sometimes the element isn't loaded yet, it'll come |
||||
//back to this when it's good to go. So skip for now. |
||||
return; |
||||
} |
||||
if (!isNaN(this.id)) { |
||||
title.innerHTML = this.$t( |
||||
'edit-ffmpeg-profile.edit-profile' |
||||
).toString(); |
||||
var ffmpegFullProfile = await ffmpegProfileApiService.getOne( |
||||
this.id.toString() |
||||
); |
||||
var result = ffmpegFullProfile[0]; |
||||
if (result !== undefined) { |
||||
//We have a profile, let's load it. |
||||
this.threadCount = result.threadCount; |
||||
this.selectedVaapiDevice = result.vaapiDevice; |
||||
this.autoDeinterlaceVideo = result.deinterlaceVideo; |
||||
this.normalizeFrameRate = result.normalizeFramerate; |
||||
this.normalizeLoudness = result.normalizeLoudness; |
||||
this.newProfile = { |
||||
name: result.name, |
||||
videoBitRate: result.videoBitrate, |
||||
videoBufferSize: result.videoBufferSize, |
||||
audioBitRate: result.audioBitrate, |
||||
audioBufferSize: result.audioBufferSize, |
||||
channels: result.audioChannels, |
||||
audioSampleRate: result.audioSampleRate |
||||
}; |
||||
|
||||
this._selectedAudioFormat = result.audioFormat; |
||||
this.selectedAudioFormat = |
||||
this.audioFormats[result.audioFormat - 1]; |
||||
this._selectedVideoFormat = result.videoFormat; |
||||
this.selectedVideoFormat = |
||||
this.videoFormats[result.videoFormat - 1]; |
||||
this._selectedResolution = result.resolutionId; |
||||
this.selectedResolution = |
||||
this.preferredResolutions[result.resolutionId - 1]; |
||||
this._selectedHardwareAcceleration = |
||||
result.hardwareAcceleration; |
||||
this.selectedHardwareAcceleration = |
||||
this.hardwareAccelerations[result.hardwareAcceleration]; |
||||
this._selectedVaapiDriver = result.vaapiDriver; |
||||
this.selectedVaapiDriver = |
||||
this.vaapiDrivers[result.vaapiDriver]; |
||||
this.applyVaapiValidation(); |
||||
this.loaded = true; |
||||
} else { |
||||
//an ID was entered (probably in the URL) that doesn't exist. Let's returnt to the profile list. |
||||
console.log('No ffmpeg profile found for ID: ' + this.id); |
||||
this.$router.push({ |
||||
name: 'ffmpeg-profiles.title' |
||||
}); |
||||
} |
||||
} else { |
||||
//new profile! |
||||
title.innerHTML = this.$t( |
||||
'edit-ffmpeg-profile.add-profile' |
||||
).toString(); |
||||
this._selectedAudioFormat = 2; |
||||
this._selectedResolution = 3; |
||||
this._selectedVideoFormat = 1; |
||||
this._selectedHardwareAcceleration = 0; |
||||
this._selectedVaapiDriver = 0; |
||||
this.selectedVaapiDevice = ''; |
||||
this.newProfile = { |
||||
name: 'New Profile', |
||||
videoBitRate: 2000, |
||||
videoBufferSize: 4000, |
||||
audioBitRate: 192, |
||||
audioBufferSize: 384, |
||||
channels: 2, |
||||
audioSampleRate: 48 |
||||
}; |
||||
this.loaded = true; |
||||
} |
||||
} |
||||
} |
||||
</script> |
@ -1,62 +0,0 @@
@@ -1,62 +0,0 @@
|
||||
<template> |
||||
<div> |
||||
<v-data-table |
||||
:headers="headers" |
||||
:items="channels" |
||||
:sort-by="['number']" |
||||
class="elevation-1" |
||||
> |
||||
<template v-slot:[`item.actions`]="{ item }"> |
||||
<v-btn icon class="mr-2" @click="editRow(item.id)"> |
||||
<v-icon>mdi-lead-pencil</v-icon> |
||||
</v-btn> |
||||
|
||||
<v-btn icon @click.stop="deleteRow(item.id)"> |
||||
<v-icon>mdi-delete</v-icon></v-btn |
||||
> |
||||
</template> |
||||
</v-data-table> |
||||
</div> |
||||
</template> |
||||
|
||||
<script lang="ts"> |
||||
import { Vue, Component } from 'vue-property-decorator'; |
||||
import { Channel } from '@/models/Channel'; |
||||
import { channelApiService } from '@/services/ChannelService'; |
||||
|
||||
@Component |
||||
export default class Channels extends Vue { |
||||
private channels: Channel[] = []; |
||||
|
||||
get headers() { |
||||
return [ |
||||
{ text: this.$t('channels.table.number'), value: 'number' }, |
||||
{ text: this.$t('channels.table.logo'), value: 'logo' }, |
||||
{ text: this.$t('channels.table.name'), value: 'name' }, |
||||
{ text: this.$t('channels.table.language'), value: 'language' }, |
||||
{ text: this.$t('channels.table.mode'), value: 'streamingMode' }, |
||||
{ |
||||
text: this.$t('channels.table.ffmpeg-profile'), |
||||
value: 'ffmpegProfile' |
||||
}, |
||||
{ text: 'Actions', value: 'actions', sortable: false } |
||||
]; |
||||
} |
||||
|
||||
deleteRow(item: any) { |
||||
let index = this.channels.findIndex((it) => it.id === item.id); |
||||
this.channels.splice(index, 1); |
||||
} |
||||
|
||||
editRow(item: any) { |
||||
let index = this.channels.findIndex((it) => it.id === item.id); |
||||
this.channels.splice(index, 1); |
||||
} |
||||
|
||||
title: string = 'Channels'; |
||||
|
||||
async mounted(): Promise<void> { |
||||
this.channels = await channelApiService.getAll(); |
||||
} |
||||
} |
||||
</script> |
@ -1,123 +0,0 @@
@@ -1,123 +0,0 @@
|
||||
<template> |
||||
<div> |
||||
<v-btn color="success" class="ma-4" @click="addRecord()">{{ |
||||
$t('ffmpeg-profiles.add-profile') |
||||
}}</v-btn> |
||||
<v-data-table |
||||
:headers="headers" |
||||
:items="ffmpegProfiles" |
||||
:sort-by="['name']" |
||||
class="elevation-1" |
||||
> |
||||
<template v-slot:[`item.actions`]="{ item }"> |
||||
<v-btn icon class="mr-2" @click="editRow(item.id)"> |
||||
<v-icon>mdi-lead-pencil</v-icon> |
||||
</v-btn> |
||||
|
||||
<v-btn icon @click.stop="deleteRecord(item.id, item.name)"> |
||||
<v-icon>mdi-delete</v-icon></v-btn |
||||
> |
||||
</template> |
||||
</v-data-table> |
||||
</div> |
||||
</template> |
||||
|
||||
<script lang="ts"> |
||||
import { Vue, Component } from 'vue-property-decorator'; |
||||
import { FFmpegProfile } from '@/models/FFmpegProfile'; |
||||
import { ffmpegProfileApiService } from '@/services/FFmpegProfileService'; |
||||
|
||||
@Component |
||||
export default class FFmpegProfiles extends Vue { |
||||
public ffmpegProfiles: FFmpegProfile[] = []; |
||||
|
||||
get headers() { |
||||
return [ |
||||
{ text: this.$t('ffmpeg-profiles.table.name'), value: 'name' }, |
||||
{ |
||||
text: this.$t('ffmpeg-profiles.table.resolution'), |
||||
value: 'resolution' |
||||
}, |
||||
{ text: this.$t('ffmpeg-profiles.table.video'), value: 'video' }, |
||||
{ text: this.$t('ffmpeg-profiles.table.audio'), value: 'audio' }, |
||||
{ |
||||
text: this.$t('ffmpeg-profiles.actions'), |
||||
value: 'actions', |
||||
sortable: false |
||||
} |
||||
]; |
||||
} |
||||
|
||||
addRecord() { |
||||
this.$router.push({ |
||||
name: 'add-ffmpeg-profile' |
||||
}); |
||||
} |
||||
|
||||
deleteRecord(record: any, recordName: any) { |
||||
this.$swal |
||||
.fire({ |
||||
title: this.$t( |
||||
'ffmpeg-profiles.delete-dialog-title' |
||||
).toString(), |
||||
// text: this.$t( |
||||
// 'Delete "' + recordName + '" FFmpeg Profile?' |
||||
// ).toString(), |
||||
text: this.$t('ffmpeg-profiles.delete-dialog-text', { |
||||
profileName: '"' + recordName + '"' |
||||
}).toString(), |
||||
icon: 'warning', |
||||
//iconColor: '#4CAF50', |
||||
showCancelButton: true, |
||||
cancelButtonText: this.$t('ffmpeg-profiles.no').toString(), |
||||
confirmButtonText: this.$t('ffmpeg-profiles.yes').toString() |
||||
}) |
||||
.then((result) => { |
||||
if (result.isConfirmed) { |
||||
let index = this.ffmpegProfiles.findIndex( |
||||
(it) => it.id === record |
||||
); |
||||
this.ffmpegProfiles.splice(index, 1); |
||||
ffmpegProfileApiService.deleteRecord(String(record)); |
||||
const Toast = this.$swal.mixin({ |
||||
toast: true, |
||||
position: 'top-end', |
||||
showConfirmButton: false, |
||||
timer: 3000, |
||||
timerProgressBar: true, |
||||
didOpen: (toast) => { |
||||
toast.addEventListener( |
||||
'mouseenter', |
||||
this.$swal.stopTimer |
||||
); |
||||
toast.addEventListener( |
||||
'mouseleave', |
||||
this.$swal.resumeTimer |
||||
); |
||||
} |
||||
}); |
||||
|
||||
Toast.fire({ |
||||
icon: 'success', |
||||
title: this.$t( |
||||
'ffmpeg-profiles.profile-deleted' |
||||
).toString() |
||||
}); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
editRow(id: any) { |
||||
this.$router.push({ |
||||
name: 'edit-ffmpeg', |
||||
query: { id: id } |
||||
}); |
||||
} |
||||
|
||||
title: string = 'FFMpeg Profiles'; |
||||
|
||||
async mounted(): Promise<void> { |
||||
this.ffmpegProfiles = await ffmpegProfileApiService.getAll(); |
||||
} |
||||
} |
||||
</script> |
@ -1,9 +0,0 @@
@@ -1,9 +0,0 @@
|
||||
<template> |
||||
<div> |
||||
<h1>{{ $t(this.$route.name) }}</h1> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
export default {}; |
||||
</script> |
@ -1,44 +0,0 @@
@@ -1,44 +0,0 @@
|
||||
{ |
||||
"compilerOptions": { |
||||
"target": "esnext", |
||||
"module": "esnext", |
||||
"strict": true, |
||||
"allowJs": true, |
||||
"experimentalDecorators": true, |
||||
"jsx": "preserve", |
||||
"moduleResolution": "node", |
||||
"skipLibCheck": true, |
||||
"esModuleInterop": true, |
||||
"allowSyntheticDefaultImports": true, |
||||
"forceConsistentCasingInFileNames": true, |
||||
"useDefineForClassFields": true, |
||||
"sourceMap": true, |
||||
"baseUrl": ".", |
||||
"types": [ |
||||
"webpack-env", |
||||
"vuetify", |
||||
"vue-sweetalert2" |
||||
], |
||||
"paths": { |
||||
"@/*": [ |
||||
"src/*" |
||||
] |
||||
}, |
||||
"lib": [ |
||||
"esnext", |
||||
"dom", |
||||
"dom.iterable", |
||||
"scripthost" |
||||
] |
||||
}, |
||||
"include": [ |
||||
"src/**/*.ts", |
||||
"src/**/*.tsx", |
||||
"src/**/*.vue", |
||||
"tests/**/*.ts", |
||||
"tests/**/*.tsx" |
||||
], |
||||
"exclude": [ |
||||
"node_modules" |
||||
] |
||||
} |
@ -1,21 +0,0 @@
@@ -1,21 +0,0 @@
|
||||
const { defineConfig } = require('@vue/cli-service'); |
||||
|
||||
module.exports = defineConfig({ |
||||
transpileDependencies: ['vuetify'], |
||||
runtimeCompiler: true, |
||||
|
||||
pwa: { |
||||
name: 'ErsatzTV' |
||||
}, |
||||
publicPath: '/v2/', |
||||
outputDir: '../wwwroot/v2', |
||||
filenameHashing: false, |
||||
pluginOptions: { |
||||
i18n: { |
||||
locale: 'en', |
||||
fallbackLocale: 'en', |
||||
localeDir: 'locales', |
||||
enableInSFC: true |
||||
} |
||||
} |
||||
}); |
Loading…
Reference in new issue