add player component
This commit is contained in:
parent
0c077f3f12
commit
355073fc91
2
Dockerfile
Normal file
2
Dockerfile
Normal file
@ -0,0 +1,2 @@
|
||||
ARG NEXTCLOUD_VERSION=20.0.0
|
||||
FROM rootlogin/nextcloud
|
BIN
img/play.png
Normal file
BIN
img/play.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
58
package-lock.json
generated
58
package-lock.json
generated
@ -5842,6 +5842,64 @@
|
||||
"flat-cache": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"file-loader": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.1.1.tgz",
|
||||
"integrity": "sha512-Klt8C4BjWSXYQAfhpYYkG4qHNTna4toMHEbWrI5IuVoxbU6uiDKeKAP99R8mmbJi3lvewn/jQBOgU4+NS3tDQw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"loader-utils": "^2.0.0",
|
||||
"schema-utils": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/json-schema": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz",
|
||||
"integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==",
|
||||
"dev": true
|
||||
},
|
||||
"ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
}
|
||||
},
|
||||
"ajv-keywords": {
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
|
||||
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
|
||||
"dev": true
|
||||
},
|
||||
"loader-utils": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
|
||||
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"big.js": "^5.2.2",
|
||||
"emojis-list": "^3.0.0",
|
||||
"json5": "^2.1.2"
|
||||
}
|
||||
},
|
||||
"schema-utils": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz",
|
||||
"integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/json-schema": "^7.0.6",
|
||||
"ajv": "^6.12.5",
|
||||
"ajv-keywords": "^3.5.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
|
@ -66,6 +66,7 @@
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-standard": "^4.0.1",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"file-loader": "^6.1.1",
|
||||
"node-sass": "^4.14.1",
|
||||
"sass-loader": "^8.0.2",
|
||||
"stylelint": "^13.7.2",
|
||||
|
86
src/components/Main.vue
Normal file
86
src/components/Main.vue
Normal file
@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<Content app-name="radio">
|
||||
<Navigation />
|
||||
<AppContent>
|
||||
<Breadcrumbs class="breadcrumbs">
|
||||
<Breadcrumb title="Home" href="/" />
|
||||
<Breadcrumb title="Top" href="/Top" />
|
||||
</Breadcrumbs>
|
||||
<Table :station-data="tableData" />
|
||||
</AppContent>
|
||||
</Content>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Content from '@nextcloud/vue/dist/Components/Content'
|
||||
import AppContent from '@nextcloud/vue/dist/Components/AppContent'
|
||||
import Breadcrumbs from '@nextcloud/vue/dist/Components/Breadcrumbs'
|
||||
import Breadcrumb from '@nextcloud/vue/dist/Components/Breadcrumb'
|
||||
import Navigation from './Navigation'
|
||||
import Table from './Table'
|
||||
|
||||
export default {
|
||||
name: 'Main',
|
||||
components: {
|
||||
Navigation,
|
||||
Content,
|
||||
AppContent,
|
||||
Breadcrumbs,
|
||||
Breadcrumb,
|
||||
Table,
|
||||
},
|
||||
data: () => ({
|
||||
tableData: [],
|
||||
offset: 0,
|
||||
}),
|
||||
mounted() {
|
||||
this.loadStations()
|
||||
this.scroll()
|
||||
},
|
||||
methods: {
|
||||
loadStations() {
|
||||
const vm = this
|
||||
this.$jquery.ajax({
|
||||
type: 'POST',
|
||||
url: 'https://de1.api.radio-browser.info/json/stations/topclick',
|
||||
data: '{ "limit": 20, "offset": 20 }',
|
||||
contentType: 'application/json',
|
||||
dataType: 'json',
|
||||
})
|
||||
.done(function(data) {
|
||||
console.log('new data')
|
||||
vm.tableData = vm.tableData.concat(data)
|
||||
vm.offset += 20
|
||||
})
|
||||
},
|
||||
scroll() {
|
||||
window.onscroll = () => {
|
||||
if ((window.innerHeight + window.scrollY) >= document.body.scrollHeight) {
|
||||
this.loadStations()
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
.app-navigation-toggle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.app-content-vue {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.breadcrumbs {
|
||||
background-color: var(--color-main-background-translucent);
|
||||
z-index: 60;
|
||||
position: sticky;
|
||||
position: -webkit-sticky;
|
||||
top: 50px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
</style>
|
41
src/components/Navigation.vue
Normal file
41
src/components/Navigation.vue
Normal file
@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<AppNavigation>
|
||||
<template id="app-radio-navigation" #list>
|
||||
<AppNavigationItem
|
||||
:to="{ name: 'TOP' }"
|
||||
icon="icon-category-dashboard"
|
||||
title="Top" />
|
||||
<AppNavigationItem
|
||||
:to="{ name: 'RECENT' }"
|
||||
icon="icon-category-monitoring"
|
||||
title="Recent" />
|
||||
<AppNavigationItem
|
||||
:to="{ name: 'FAVORITES' }"
|
||||
icon="icon-favorite"
|
||||
title="Favorites" />
|
||||
<AppNavigationItem
|
||||
:to="{ name: 'CATEGORIES' }"
|
||||
icon="icon-files-dark"
|
||||
title="Categories" />
|
||||
</template>
|
||||
<template #footer>
|
||||
<Player :pinned="true" />
|
||||
</template>
|
||||
</AppNavigation>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AppNavigation from '@nextcloud/vue/dist/Components/AppNavigation'
|
||||
import AppNavigationItem from '@nextcloud/vue/dist/Components/AppNavigationItem'
|
||||
import Player from './Player'
|
||||
|
||||
export default {
|
||||
name: 'Navigation',
|
||||
components: {
|
||||
AppNavigation,
|
||||
AppNavigationItem,
|
||||
Player,
|
||||
},
|
||||
}
|
||||
|
||||
</script>
|
88
src/components/Player.vue
Normal file
88
src/components/Player.vue
Normal file
@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<div id="app-settings">
|
||||
<button id="playbutton" class="play" :style="playerIcon" />
|
||||
<div id="volumeicon" class="full" />
|
||||
<div id="volumeslider" />
|
||||
<span id="station_metadata" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
playerIcon: {
|
||||
backgroundImage: `url( ${require('../../img/play.png')} )`,
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
#playbutton{
|
||||
height:50px;
|
||||
width: 50px;
|
||||
margin: 10px;
|
||||
border: none;
|
||||
background-size: 100%;
|
||||
background-position: center;
|
||||
float: left;
|
||||
transition: background-image 0.4s ease-in-out;
|
||||
}
|
||||
|
||||
.buffering {
|
||||
animation:spin 4s linear infinite;
|
||||
/* background: url('../img/wheel.png') no-repeat; */
|
||||
transition: background-image 0.4s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes spin { 100% { transform:rotate(360deg); } }
|
||||
|
||||
#station_metadata{
|
||||
margin: 2px 20px 5px 20px;
|
||||
padding-left: 5px;
|
||||
white-space:nowrap;
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
.play{
|
||||
/* background: url('../img/play.png') no-repeat; */
|
||||
}
|
||||
|
||||
.pause{
|
||||
/* background: url('../img/pause.png') no-repeat; */
|
||||
}
|
||||
|
||||
#volumeicon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
position: relative;
|
||||
left: 75px;
|
||||
top: 14px;
|
||||
}
|
||||
|
||||
#volumeicon.full {
|
||||
/* background-image: url('../img/sound_full.png'); */
|
||||
}
|
||||
|
||||
#volumeicon.mid {
|
||||
/* background-image: url('../img/sound_mid.png'); */
|
||||
}
|
||||
|
||||
#volumeicon.silent {
|
||||
/* background-image: url('../img/sound_silent.png'); */
|
||||
}
|
||||
|
||||
#volumeslider{
|
||||
width: 170px;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
left: 40px;
|
||||
top: -7px;
|
||||
}
|
||||
|
||||
</style>
|
136
src/components/Table.vue
Normal file
136
src/components/Table.vue
Normal file
@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<table id="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th />
|
||||
<th class="nameColumn">
|
||||
Name
|
||||
</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody v-if="stationData">
|
||||
<tr v-for="(station, idx) in stationData" :key="idx">
|
||||
<td>
|
||||
<div class="stationIcon"
|
||||
:style="{ backgroundImage: `url('${ station.favicon }')` }" />
|
||||
</td>
|
||||
<td class="filenameColumn">
|
||||
<span class="innernametext">
|
||||
{{ station.name }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="actionColumn">
|
||||
<Actions>
|
||||
<ActionButton icon="icon-star" :close-after-click="true">
|
||||
Add to favorites
|
||||
</ActionButton>
|
||||
<ActionButton icon="icon-info" :close-after-click="true">
|
||||
Details
|
||||
</ActionButton>
|
||||
</Actions>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Actions from '@nextcloud/vue/dist/Components/Actions'
|
||||
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
|
||||
|
||||
export default {
|
||||
name: 'Table',
|
||||
components: {
|
||||
Actions,
|
||||
ActionButton,
|
||||
},
|
||||
props: {
|
||||
stationData: {
|
||||
type: Array,
|
||||
default() { return [] },
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
min-width: 250px;
|
||||
|
||||
thead {
|
||||
background-color: var(--color-main-background-translucent);
|
||||
z-index: 60;
|
||||
position: sticky;
|
||||
position: -webkit-sticky;
|
||||
top: 99px;
|
||||
|
||||
th {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
padding: 15px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
th, th a {
|
||||
color: var(--color-text-maxcontrast);
|
||||
}
|
||||
|
||||
th.nameColumn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
tbody {
|
||||
|
||||
td {
|
||||
padding: 0 15px;
|
||||
font-style: normal;
|
||||
background-position: 8px center;
|
||||
background-repeat: no-repeat;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
tr {
|
||||
height: 51px;
|
||||
background-color: var(--color-background-light);
|
||||
|
||||
tr:hover, tr:focus, tr.mouseOver td {
|
||||
background-color: var(--color-background-hover);
|
||||
}
|
||||
}
|
||||
|
||||
tr td:first-child {
|
||||
padding-left: 40px;
|
||||
width: 32px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
td.filenameColumn .innernametext {
|
||||
color: var(--color-main-text);
|
||||
}
|
||||
|
||||
.stationIcon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.actionColumn {
|
||||
width: auto;
|
||||
padding-right: 40px;
|
||||
}
|
||||
|
||||
.filenameColumn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</style>
|
40
src/router.js
Normal file
40
src/router.js
Normal file
@ -0,0 +1,40 @@
|
||||
import Vue from 'vue'
|
||||
import Router from 'vue-router'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
|
||||
import Main from './components/Main'
|
||||
|
||||
Vue.use(Router)
|
||||
|
||||
export default new Router({
|
||||
mode: 'history',
|
||||
base: generateUrl('/apps/radio'),
|
||||
linkActiveClass: 'active',
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
component: Main,
|
||||
name: 'TOP',
|
||||
},
|
||||
{
|
||||
path: '/top',
|
||||
component: Main,
|
||||
name: 'TOP',
|
||||
},
|
||||
{
|
||||
path: '/recent',
|
||||
component: Main,
|
||||
name: 'RECENT',
|
||||
},
|
||||
{
|
||||
path: '/favorites',
|
||||
component: Main,
|
||||
name: 'FAVORITES',
|
||||
},
|
||||
{
|
||||
path: '/categories',
|
||||
component: Main,
|
||||
name: 'CATEGORIES',
|
||||
},
|
||||
],
|
||||
})
|
18
webpack.js
18
webpack.js
@ -1,3 +1,19 @@
|
||||
const { merge } = require('webpack-merge')
|
||||
const webpackConfig = require('@nextcloud/webpack-vue-config')
|
||||
|
||||
module.exports = webpackConfig
|
||||
const config = {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(png|jpg|gif)$/, loader: 'file-loader?name=./images/[name].[ext]',
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
const mergedConfigs = merge(config, webpackConfig)
|
||||
|
||||
// Remove duplicate rules by the `test` key
|
||||
mergedConfigs.module.rules = mergedConfigs.module.rules.filter((v, i, a) => a.findIndex(t => (t.test.toString() === v.test.toString())) === i)
|
||||
|
||||
module.exports = mergedConfigs
|
||||
|
Loading…
Reference in New Issue
Block a user