add player component

This commit is contained in:
Jonas Heinrich 2020-10-17 20:06:57 +02:00
parent 0c077f3f12
commit 355073fc91
10 changed files with 469 additions and 1 deletions

2
Dockerfile Normal file
View File

@ -0,0 +1,2 @@
ARG NEXTCLOUD_VERSION=20.0.0
FROM rootlogin/nextcloud

BIN
img/play.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

58
package-lock.json generated
View File

@ -5842,6 +5842,64 @@
"flat-cache": "^2.0.1" "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": { "file-uri-to-path": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",

View File

@ -66,6 +66,7 @@
"eslint-plugin-promise": "^4.2.1", "eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1", "eslint-plugin-standard": "^4.0.1",
"eslint-plugin-vue": "^6.2.2", "eslint-plugin-vue": "^6.2.2",
"file-loader": "^6.1.1",
"node-sass": "^4.14.1", "node-sass": "^4.14.1",
"sass-loader": "^8.0.2", "sass-loader": "^8.0.2",
"stylelint": "^13.7.2", "stylelint": "^13.7.2",

86
src/components/Main.vue Normal file
View 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>

View 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
View 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
View 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
View 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',
},
],
})

View File

@ -1,3 +1,19 @@
const { merge } = require('webpack-merge')
const webpackConfig = require('@nextcloud/webpack-vue-config') 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