move
45
.drone.yml
Normal file
@ -0,0 +1,45 @@
|
||||
kind: pipeline
|
||||
name: default
|
||||
|
||||
clone:
|
||||
disable: true
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: node:latest
|
||||
commands:
|
||||
- git clone https://gitea.com/artemanufrij/core.git
|
||||
- cd core
|
||||
- npm install
|
||||
- mkdir app
|
||||
- cd app
|
||||
- git clone https://gitea.com/WebPlay/client.git
|
||||
- cd client
|
||||
- git checkout ${DRONE_BRANCH}
|
||||
- sed -i '/"electron":/d' ./package.json
|
||||
- sed -i '/"electron-builder":/d' ./package.json
|
||||
- sed -i '/"express":/d' ./package.json
|
||||
- npm install
|
||||
- npm run build
|
||||
- mv dist ../../..
|
||||
|
||||
- name: deploy
|
||||
image: appleboy/drone-scp
|
||||
when:
|
||||
branch: master
|
||||
event: push
|
||||
settings:
|
||||
host:
|
||||
from_secret: drone_server_host
|
||||
username:
|
||||
from_secret: drone_server_user
|
||||
password:
|
||||
from_secret: drone_server_pass
|
||||
port:
|
||||
from_secret: drone_server_port
|
||||
command_timeout: 2m
|
||||
rm: true
|
||||
target: /var/www/drone/app/webplay
|
||||
source:
|
||||
- dist/*
|
||||
strip_components: 1
|
22
.gitignore
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
dist
|
||||
release
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
67
README.md
Normal file
@ -0,0 +1,67 @@
|
||||
# WebPlay Client
|
||||
[![Build Status](https://drone.anufrij.de/api/badges/WebPlay/client/status.svg)](https://drone.anufrij.de/WebPlay/client)
|
||||
|
||||
For elementary OS users I created a similar native application written in Vala: https://github.com/artemanufrij/playmymusic
|
||||
|
||||
Web client for [WebPlay Server](/WebPlay/server)
|
||||
|
||||
## Screenshots
|
||||
<p align="center">
|
||||
<img src="screenshots/mobile_home.png" width="20%"/>
|
||||
<img src="screenshots/mobile_album.png" width="20%"/>
|
||||
<img src="screenshots/mobile_artist.png" width="20%"/>
|
||||
<img src="screenshots/mobile_boxes.png" width="20%"/>
|
||||
</p>
|
||||
|
||||
![Albums](screenshots/home.png)
|
||||
|
||||
![Albums](screenshots/albums.png)
|
||||
|
||||
![Artists](screenshots/artists.png)
|
||||
|
||||
![Videos](screenshots/videos.png)
|
||||
|
||||
## How to install your onw instance
|
||||
[Documentation](/WebPlay/docker#requirements)
|
||||
|
||||
### Requirements
|
||||
``` bash
|
||||
# Debian based
|
||||
sudo apt install git nodejs
|
||||
|
||||
# Arch based
|
||||
sudo pacman -S git nodejs
|
||||
```
|
||||
|
||||
### Clone Repository
|
||||
``` bash
|
||||
# clone repository
|
||||
git clone https://gitea.com/WebPlay/client.git webplay-client
|
||||
|
||||
# move into directory
|
||||
cd webplay-client
|
||||
```
|
||||
|
||||
### Build Setup
|
||||
``` bash
|
||||
# install dependencies
|
||||
npm install
|
||||
|
||||
# serve with hot reload at localhost:8080
|
||||
npm run serve
|
||||
|
||||
# build for production with minification
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Build an Electron App (optional)
|
||||
``` bash
|
||||
# package into an AppImage
|
||||
npm run linux-appimage
|
||||
```
|
||||
|
||||
## Support
|
||||
Join our Matrix room: <a href="https://matrix.to/#/#WebPlay:matrix.anufrij.de">#WebPlay:matrix.anufrij.de</a>
|
||||
|
||||
## Donate
|
||||
<a href="https://www.paypal.me/ArtemAnufrij">PayPal</a> | <a href="https://liberapay.com/Artem/donate">LiberaPay</a> | <a href="https://www.patreon.com/ArtemAnufrij">Patreon</a>
|
5
babel.config.js
Normal file
@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
1
build/icons/128x128.png
Symbolic link
@ -0,0 +1 @@
|
||||
../../public/static/icon_128.png
|
1
build/icons/16x16.png
Symbolic link
@ -0,0 +1 @@
|
||||
../../public/static/icon_16.png
|
1
build/icons/24x24.png
Symbolic link
@ -0,0 +1 @@
|
||||
../../public/static/icon_24.png
|
1
build/icons/32x32.png
Symbolic link
@ -0,0 +1 @@
|
||||
../../public/static/icon_32.png
|
1
build/icons/48x48.png
Symbolic link
@ -0,0 +1 @@
|
||||
../../public/static/icon_48.png
|
1
build/icons/64x64.png
Symbolic link
@ -0,0 +1 @@
|
||||
../../public/static/icon_64.png
|
75
electron.js
Normal file
@ -0,0 +1,75 @@
|
||||
const electron = require("electron");
|
||||
const { app, BrowserWindow } = electron;
|
||||
const express = require("express");
|
||||
const body = require("body-parser");
|
||||
const fs = require("fs");
|
||||
const backend = express();
|
||||
|
||||
let win = null;
|
||||
|
||||
backend.use(express.static(__dirname + "/dist"));
|
||||
backend.use(body.json());
|
||||
|
||||
backend.post("/settings", (req, res) => {
|
||||
var config = JSON.parse(
|
||||
fs.readFileSync(__dirname + "/dist/config.json", "utf8")
|
||||
);
|
||||
config.backend = req.body.backend;
|
||||
fs.writeFile(__dirname + "/dist/config.json", JSON.stringify(config), err => {
|
||||
if (err) throw err;
|
||||
});
|
||||
res.end();
|
||||
});
|
||||
|
||||
let server = backend.listen(0, () => {
|
||||
console.log("LISTEN")
|
||||
app.whenReady().then(() => {
|
||||
createWindow()
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
||||
})
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
app.on("window-all-closed", () => {
|
||||
if (process.platform != "darwin") {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
function createWindow() {
|
||||
console.log("CREATE WINDOW")
|
||||
const WINDOW_WIDTH = 1200;
|
||||
const WINDOW_HEIGHT = 680;
|
||||
|
||||
let bounds = electron.screen.getPrimaryDisplay().bounds;
|
||||
let x = bounds.x + (bounds.width - WINDOW_WIDTH) / 2;
|
||||
let y = bounds.y + (bounds.height - WINDOW_HEIGHT) / 2;
|
||||
|
||||
win = new BrowserWindow({
|
||||
width: WINDOW_WIDTH,
|
||||
height: WINDOW_HEIGHT,
|
||||
x: x,
|
||||
y: y,
|
||||
icon: __dirname + "/dist/static/icon_48.png"
|
||||
});
|
||||
|
||||
win.setMenuBarVisibility(false);
|
||||
|
||||
let session = win.webContents.session;
|
||||
/* session.clearCache(() => {
|
||||
let url = "http://localhost:" + server.address().port;
|
||||
console.log(url)
|
||||
win.loadURL(url);
|
||||
});
|
||||
*/
|
||||
|
||||
let url = "http://localhost:" + server.address().port;
|
||||
console.log(url)
|
||||
win.loadURL(url);
|
||||
|
||||
win.on("closed", () => {
|
||||
win = null;
|
||||
});
|
||||
}
|
23885
package-lock.json
generated
Normal file
89
package.json
Normal file
@ -0,0 +1,89 @@
|
||||
{
|
||||
"name": "webplay-client",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"description": "Web client for WebPlay Server",
|
||||
"author": "Artem Anufrij <artem.anufrij@live.de>",
|
||||
"scripts": {
|
||||
"serve": "NODE_OPTIONS=--openssl-legacy-provider vue-cli-service serve",
|
||||
"build": "NODE_OPTIONS=--openssl-legacy-provider vue-cli-service build",
|
||||
"lint": "vue-cli-service lint",
|
||||
"electron": "electron .",
|
||||
"linux-appimage": "electron-builder"
|
||||
},
|
||||
"main": "electron.js",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^6.2.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
||||
"@fortawesome/vue-fontawesome": "^3.0.2",
|
||||
"axios": "^1.2.2",
|
||||
"body-parser": "^1.19.1",
|
||||
"core-js": "^3.27.1",
|
||||
"express": "^4.18.2",
|
||||
"mp3tag.js": "^3.3.2",
|
||||
"vue": "*",
|
||||
"vue-router": "^4.1.6",
|
||||
"vuex": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "^7.19.1",
|
||||
"@vue/cli-plugin-babel": "^5.0.8",
|
||||
"@vue/cli-plugin-eslint": "^5.0.8",
|
||||
"@vue/cli-service": "^5.0.8",
|
||||
"electron": "^22.0.0",
|
||||
"electron-builder": "^23.6.0",
|
||||
"eslint": "^8.31.0",
|
||||
"eslint-plugin-vue": "^9.8.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"rules": {
|
||||
"no-undef": "off",
|
||||
"no-console": "off"
|
||||
},
|
||||
"parserOptions": {
|
||||
"parser": "@babel/eslint-parser"
|
||||
}
|
||||
},
|
||||
"postcss": {
|
||||
"plugins": {
|
||||
"autoprefixer": {}
|
||||
}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions"
|
||||
],
|
||||
"build": {
|
||||
"appId": "de.anufrij.webplay",
|
||||
"directories": {
|
||||
"output": "release"
|
||||
},
|
||||
"linux": {
|
||||
"target": [
|
||||
{
|
||||
"target": "AppImage"
|
||||
}
|
||||
],
|
||||
"category": "AudioVideo",
|
||||
"synopsis": "Generate your PMP",
|
||||
"packageCategory": "AudioVideo",
|
||||
"artifactName": "${productName}.${ext}",
|
||||
"desktop": {
|
||||
"Encoding": "UTF-8",
|
||||
"Name": "WebPlay",
|
||||
"Type": "Application",
|
||||
"Categories": "AudioVideo",
|
||||
"Terminal": "false"
|
||||
}
|
||||
}
|
||||
},
|
||||
"productName": "WebPlay Client"
|
||||
}
|
5
public/config.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"backend_de": "http://localhost:31204",
|
||||
"backend_dev": "https://webplay.rocks",
|
||||
"backend": "https://webplay.rocks"
|
||||
}
|
29
public/index.html
Normal file
@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="Cache-control" content="no-cache" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
|
||||
<meta name="theme-color" content="#31363b" />
|
||||
<link rel="icon" type="image/svg+xml" href="static/icon_16.svg" sizes="16x16" />
|
||||
<link rel="icon" type="image/svg+xml" href="static/icon_24.svg" sizes="24x24" />
|
||||
<link rel="icon" type="image/svg+xml" href="static/icon_32.svg" sizes="32x32" />
|
||||
<link rel="icon" type="image/svg+xml" href="static/icon_48.svg" sizes="48x48" />
|
||||
<link rel="icon" type="image/svg+xml" href="static/icon_64.svg" sizes="any" />
|
||||
<title>WebPlay.rocks</title>
|
||||
<link rel="manifest" href="manifest.json" />
|
||||
<link rel="stylesheet" type="text/css" href="static/style.css" />
|
||||
<link rel="stylesheet" type="text/css" href="static/app.css" />
|
||||
<link rel="stylesheet" type="text/css" href="static/css/dialogRadios.css" />
|
||||
<link rel="stylesheet" type="text/css" href="static/css/dialogSettings.css" />
|
||||
<link rel="stylesheet" type="text/css" href="static/css/dialogProfile.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
49
public/manifest.json
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"short_name": "WebPlay",
|
||||
"name": "WebPlay by Artem Anufrij",
|
||||
"start_url": "/",
|
||||
"background_color": "#31363b",
|
||||
"display": "standalone",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/static/icon_16.png",
|
||||
"sizes": "16x16",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/static/icon_24.png",
|
||||
"sizes": "24x24",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/static/icon_32.png",
|
||||
"sizes": "32x32",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/static/icon_48.png",
|
||||
"sizes": "48x48",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/static/icon_64.png",
|
||||
"sizes": "64x64",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/static/icon_96.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/static/icon_128.png",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/static/icon_192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
741
public/static/app.css
Normal file
@ -0,0 +1,741 @@
|
||||
body {
|
||||
overflow-y: hidden;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#app-body {
|
||||
overflow-y: overlay;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight: 200;
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input:invalid {
|
||||
border-color: var(--danger);
|
||||
}
|
||||
|
||||
footer {
|
||||
opacity: 0.5;
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
td.fillCell {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
td.fillCell>* {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.configValues td>* {
|
||||
margin: 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.configValues td:first-child {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.configValues td:last-child {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: var(--selected);
|
||||
}
|
||||
|
||||
#albums,
|
||||
#artists,
|
||||
#boxes,
|
||||
#radios,
|
||||
#search,
|
||||
#welcome {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-wrap: wrap;
|
||||
overflow-y: overlay;
|
||||
align-content: flex-start;
|
||||
}
|
||||
|
||||
.componentTitle {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
background-color: #ffffffb0;
|
||||
width: 100%;
|
||||
padding: 2px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.favourite {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.favourite.active {
|
||||
color: var(--yellow);
|
||||
}
|
||||
|
||||
/*
|
||||
DIALOGS
|
||||
*/
|
||||
|
||||
#artistViewer #background,
|
||||
#albumViewer #background,
|
||||
#boxViewer #background {
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
top: -16px;
|
||||
left: -16px;
|
||||
right: -16px;
|
||||
bottom: -16px;
|
||||
position: absolute;
|
||||
filter: blur(8px);
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
/*
|
||||
ARTIST
|
||||
*/
|
||||
.artistCover {
|
||||
height: 128px;
|
||||
width: 256px;
|
||||
background: var(--white);
|
||||
}
|
||||
|
||||
.artistName {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
margin: auto;
|
||||
max-width: 256px;
|
||||
}
|
||||
|
||||
/*
|
||||
ALBUM
|
||||
*/
|
||||
.container {
|
||||
align-self: flex-start;
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
animation: fadeIn ease 0.3s;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideToTop {
|
||||
from {
|
||||
margin-top: -47px;
|
||||
}
|
||||
|
||||
to {
|
||||
margin-top: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideOverTop {
|
||||
from {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
to {
|
||||
margin-top: -47px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes zoomIn {
|
||||
from {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
.album>*,
|
||||
.album-line>*,
|
||||
.artist>*,
|
||||
.box>*,
|
||||
.radio>* {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.albumCover,
|
||||
.radioCover {
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.albumCover.line {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.albumTitle,
|
||||
.radioTitle {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
max-width: 128px;
|
||||
}
|
||||
|
||||
.radioUrl {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
max-width: 128px;
|
||||
font-size: 0.8rem;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
/*
|
||||
TRACK
|
||||
*/
|
||||
.tracks,
|
||||
.videos {
|
||||
display: block;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
overflow-y: auto;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.track,
|
||||
.video {
|
||||
max-width: 100%;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.track {
|
||||
padding: 12px 8px;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.track:hover:not(.selected),
|
||||
.video:hover:not(.selected) {
|
||||
background-color: var(--button-hover);
|
||||
}
|
||||
|
||||
.track input {
|
||||
width: 100%;
|
||||
margin: 4px 0 0 0;
|
||||
}
|
||||
|
||||
.trackDetails {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.trackCover {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
flex-shrink: 0;
|
||||
background: var(--white);
|
||||
}
|
||||
|
||||
.trackCover,
|
||||
.videoCover {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.trackTitle,
|
||||
.videoTitle {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.radioCover {
|
||||
border-radius: 64px;
|
||||
}
|
||||
|
||||
.radioTitle {
|
||||
margin-top: 4px;
|
||||
width: 100%;
|
||||
padding: 2px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/*
|
||||
BOX
|
||||
*/
|
||||
.boxCover {
|
||||
width: 128px;
|
||||
height: 180px;
|
||||
background: var(--white);
|
||||
}
|
||||
|
||||
.boxTitle {
|
||||
overflow: hidden;
|
||||
margin: auto;
|
||||
max-width: 128px;
|
||||
}
|
||||
|
||||
.video {
|
||||
margin: 0;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.videoCover {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.videoTitle {
|
||||
width: auto;
|
||||
left: 12px;
|
||||
right: 12px;
|
||||
bottom: 12px;
|
||||
}
|
||||
|
||||
/*
|
||||
WELCOME
|
||||
*/
|
||||
|
||||
#welcome #welcomeLeft {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
#welcome h3 {
|
||||
margin-bottom: 0px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
#welcome #banner {
|
||||
min-height: 256px;
|
||||
background: radial-gradient(circle farthest-side at left 5% top 30px,
|
||||
var(--dark90),
|
||||
var(--dark));
|
||||
color: var(--white);
|
||||
border-bottom: 1px solid var(--light-border);
|
||||
}
|
||||
|
||||
#welcome #banner h1 {
|
||||
text-shadow: 1px 4px 2px black;
|
||||
}
|
||||
|
||||
/*
|
||||
HISTORY
|
||||
*/
|
||||
#history {
|
||||
max-height: 110px;
|
||||
min-height: 110px;
|
||||
overflow-y: hidden;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-content: flex-start;
|
||||
}
|
||||
|
||||
#history .boxCover {
|
||||
width: 68px;
|
||||
height: 96px;
|
||||
}
|
||||
|
||||
#history .boxTitle {
|
||||
width: 68px;
|
||||
}
|
||||
|
||||
#history .albumCover,
|
||||
#history .radioCover {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
}
|
||||
|
||||
#history .albumTitle,
|
||||
#history .radioTitle {
|
||||
max-width: 96px;
|
||||
}
|
||||
|
||||
#history .artistCover {
|
||||
width: 192px;
|
||||
height: 96px;
|
||||
}
|
||||
|
||||
#history .artistName {
|
||||
width: 192px;
|
||||
}
|
||||
|
||||
@media (max-width: 480px),
|
||||
(max-height: 480px) {
|
||||
#history {
|
||||
max-height: 220px;
|
||||
min-height: 110px;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
NEWEST
|
||||
*/
|
||||
#newest {
|
||||
border-left: 1px solid var(--dark-border);
|
||||
width: 228px;
|
||||
background-color: var(--white);
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
#newest .boxCover {
|
||||
width: 90px;
|
||||
height: 128px;
|
||||
}
|
||||
|
||||
#newest .boxTitle {
|
||||
width: 90px;
|
||||
}
|
||||
|
||||
#mostListened .track {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
#newestVideos {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
#mostViewed {
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
#mostViewedVideos {
|
||||
flex-wrap: wrap;
|
||||
max-height: 292px;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
#mostViewedVideos .video {
|
||||
padding: 8px;
|
||||
flex-grow: 1;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
#mostViewedVideos .video:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
#mostViewedVideos .videoCover {
|
||||
width: 156px;
|
||||
}
|
||||
|
||||
#mostViewedVideos .videoTitle {
|
||||
width: 156px;
|
||||
left: initial;
|
||||
right: initial;
|
||||
bottom: 8px;
|
||||
}
|
||||
|
||||
/*
|
||||
SEARCH
|
||||
*/
|
||||
#search .boxCover {
|
||||
width: 90px;
|
||||
height: 128px;
|
||||
}
|
||||
|
||||
#search .boxTitle {
|
||||
width: 90px;
|
||||
}
|
||||
|
||||
#search .radioTitle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#search .track {
|
||||
padding: 0;
|
||||
margin: 12px;
|
||||
justify-content: flex-end;
|
||||
height: 128px;
|
||||
}
|
||||
|
||||
#search .track .trackDetails {
|
||||
flex-direction: column-reverse;
|
||||
max-height: 128px;
|
||||
}
|
||||
|
||||
#search .track .trackDetails .trackTitle {
|
||||
white-space: initial;
|
||||
max-lines: 3;
|
||||
box-sizing: content-box;
|
||||
max-width: 64px;
|
||||
max-height: 96px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#search .track .trackDetails img {
|
||||
margin: 0;
|
||||
margin-top: 4px;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
#search .video .videoCover {
|
||||
height: 128px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#search .video:hover,
|
||||
#search .track:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
#login,
|
||||
#setup {
|
||||
background: radial-gradient(circle farthest-side at left 5% top 30px,
|
||||
var(--dark90),
|
||||
var(--dark));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow-y: auto;
|
||||
flex-grow: 1;
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
#login form,
|
||||
#setup form {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#loginLogin button,
|
||||
#loginRegestration button,
|
||||
#setup button {
|
||||
display: initial;
|
||||
color: var(--white);
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
#loginLogin input {
|
||||
font-size: medium;
|
||||
}
|
||||
|
||||
#login h1,
|
||||
#setup h1 {
|
||||
margin-top: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#loginViewer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
#loginLogo {
|
||||
width: 192px;
|
||||
height: 192px;
|
||||
}
|
||||
|
||||
#loginLogin {
|
||||
padding-right: 32px;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
#loginContinue {
|
||||
border-left: 1px solid var(--light-border);
|
||||
padding-left: 32px;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#loginGuest button {
|
||||
border-radius: 0px;
|
||||
font-size: x-large;
|
||||
padding: 4px;
|
||||
padding-right: 12px;
|
||||
border-bottom-right-radius: 29px;
|
||||
border-top-right-radius: 29px;
|
||||
border-left: 4px solid var(--primary);
|
||||
}
|
||||
|
||||
#loginGuest button:hover {
|
||||
background-color: var(--white);
|
||||
}
|
||||
|
||||
.loadingItem {
|
||||
width: 100%;
|
||||
animation: glow 1s infinite alternate;
|
||||
text-align: center;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.loading {
|
||||
animation: glow 1s infinite alternate;
|
||||
}
|
||||
|
||||
.z1 {
|
||||
z-index: 1;
|
||||
}
|
||||
.z2 {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
@keyframes glow {
|
||||
to {
|
||||
background-color: var(--selected50);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
#app-body {
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#artists .artistCover {
|
||||
min-width: 100%;
|
||||
height: initial;
|
||||
min-height: 128px;
|
||||
}
|
||||
|
||||
#artists .artistName {
|
||||
max-width: initial;
|
||||
}
|
||||
|
||||
nav>div:last-child>input {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
#loginLogin {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#loginContinue {
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#loginViewer {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#newest {
|
||||
margin-top: 8px;
|
||||
border-top: 1px solid var(--dark-border);
|
||||
width: 100%;
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
#mostListened .trackTitle {
|
||||
max-width: 256px;
|
||||
}
|
||||
|
||||
#mostUsed {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#mostViewedVideos .video {
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px),
|
||||
(max-height: 480px) {
|
||||
|
||||
.albumCover,
|
||||
.radioCover {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
}
|
||||
|
||||
.albumTitle,
|
||||
.radioTitle {
|
||||
max-width: 96px;
|
||||
}
|
||||
|
||||
#search .boxCover {
|
||||
width: 68px;
|
||||
height: 96px;
|
||||
}
|
||||
|
||||
#search .boxTitle {
|
||||
width: 68px;
|
||||
}
|
||||
|
||||
#search .artistCover {
|
||||
width: 192px;
|
||||
height: 96px;
|
||||
}
|
||||
|
||||
#search .artistName {
|
||||
width: 192px;
|
||||
}
|
||||
|
||||
#search .track {
|
||||
height: 96px;
|
||||
}
|
||||
|
||||
#search .video .videoCover {
|
||||
height: 96px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#boxes .boxCover {
|
||||
width: 96px;
|
||||
height: 145px;
|
||||
}
|
||||
|
||||
#boxes .boxTitle {
|
||||
width: 96px;
|
||||
}
|
||||
|
||||
#loginLogo {
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
}
|
||||
|
||||
#login button {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
#welcome #banner {
|
||||
height: 160px;
|
||||
min-height: 160px;
|
||||
}
|
||||
|
||||
#newest {
|
||||
min-height: initial;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 480px) {
|
||||
#loginLogo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#login h1 {
|
||||
margin-bottom: 12px;
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
15
public/static/css/dialogProfile.css
Normal file
@ -0,0 +1,15 @@
|
||||
#profileBody {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#profileBody #newUserPass {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#profileBody input[type="text"],
|
||||
#profileBody input[type="password"] {
|
||||
display: block;
|
||||
margin: 0;
|
||||
}
|
30
public/static/css/dialogRadios.css
Normal file
@ -0,0 +1,30 @@
|
||||
#newRadio {
|
||||
display: flex;
|
||||
}
|
||||
#radiosBody th {
|
||||
text-align: left;
|
||||
padding: 2px;
|
||||
}
|
||||
#radiosBody th.maxWidth {
|
||||
width: 99%;
|
||||
}
|
||||
#radiosBody td {
|
||||
white-space: nowrap;
|
||||
padding: 2px;
|
||||
}
|
||||
#radiosBody table button {
|
||||
opacity: 0.25;
|
||||
}
|
||||
#radiosBody table button:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
#radiosBody .radioCover {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
cursor: pointer;
|
||||
}
|
||||
#radiosBody input[type="file"] {
|
||||
position: absolute;
|
||||
top: -500px;
|
||||
z-index: -1;
|
||||
}
|
4
public/static/css/dialogSettings.css
Normal file
@ -0,0 +1,4 @@
|
||||
#settingsBody th,
|
||||
#settingsBody td {
|
||||
padding: 2px;
|
||||
}
|
BIN
public/static/icon_128.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
public/static/icon_16.png
Normal file
After Width: | Height: | Size: 637 B |
185
public/static/icon_16.svg
Normal file
@ -0,0 +1,185 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="16"
|
||||
height="16"
|
||||
version="1.1"
|
||||
id="svg38"
|
||||
sodipodi:docname="icon_16.svg"
|
||||
inkscape:version="0.92.4 5da689c313, 2019-01-14"
|
||||
inkscape:export-filename="/home/djoma/Projekte/artemanufrij/app/webplay-client/public/static/icon_16.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<metadata
|
||||
id="metadata42">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1477"
|
||||
inkscape:window-height="1009"
|
||||
id="namedview40"
|
||||
showgrid="false"
|
||||
inkscape:zoom="20.85965"
|
||||
inkscape:cx="-6.9605375"
|
||||
inkscape:cy="13.746223"
|
||||
inkscape:window-x="216"
|
||||
inkscape:window-y="140"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg38" />
|
||||
<defs
|
||||
id="defs18">
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient851">
|
||||
<stop
|
||||
style="stop-color:#535c65;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop847" />
|
||||
<stop
|
||||
style="stop-color:#31363b;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop849" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient835">
|
||||
<stop
|
||||
style="stop-color:#a3a3a3;stop-opacity:0.25098041"
|
||||
offset="0"
|
||||
id="stop831" />
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:0.25098041"
|
||||
offset="1"
|
||||
id="stop833" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient4224"
|
||||
x1="24"
|
||||
x2="24"
|
||||
y1="5"
|
||||
y2="43"
|
||||
gradientTransform="matrix(0.2972973,0,0,0.2972973,0.86486973,0.86486734)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient3924-4-8" />
|
||||
<linearGradient
|
||||
id="linearGradient3924-4-8">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#ffffff;stop-opacity:1"
|
||||
id="stop3" />
|
||||
<stop
|
||||
offset=".063"
|
||||
style="stop-color:#ffffff;stop-opacity:0.23529412"
|
||||
id="stop5" />
|
||||
<stop
|
||||
offset=".951"
|
||||
style="stop-color:#ffffff;stop-opacity:0.15686275"
|
||||
id="stop7" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#ffffff;stop-opacity:0.39215687"
|
||||
id="stop9" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient4256">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#1777ba;stop-opacity:1"
|
||||
id="stop12" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#5cb7f6;stop-opacity:1"
|
||||
id="stop14" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient4221"
|
||||
x1="8"
|
||||
x2="8"
|
||||
y1="14.468"
|
||||
y2="1"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient4256" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient835"
|
||||
id="linearGradient837"
|
||||
x1="7"
|
||||
y1="1"
|
||||
x2="7"
|
||||
y2="15"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient851"
|
||||
id="radialGradient853"
|
||||
cx="8"
|
||||
cy="1.0967201"
|
||||
fx="8"
|
||||
fy="1.0967201"
|
||||
r="7"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0,1.9929111,-2.034965,0,10.231787,-14.846569)" />
|
||||
</defs>
|
||||
<rect
|
||||
style="fill:url(#radialGradient853);fill-opacity:1;stroke:none;stroke-width:2.10178804;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect827"
|
||||
width="14"
|
||||
height="14"
|
||||
x="1"
|
||||
y="1"
|
||||
ry="1" />
|
||||
<path
|
||||
d="M 8,3 C 5.6878777,3 4.1780616,4.3027135 3.5507812,5.59375 2.9564294,6.8170148 3.0098336,7.953267 3.0117188,8 L 3,8 A 1.0001,1.0001 0 0 0 2,9 l 0,2 a 1.0001,1.0001 0 0 0 1,1 l 0.5859375,0 0.7070313,0.707031 A 1.0001,1.0001 0 0 0 5,13 1.0001,1.0001 0 0 0 6,14 l 1,0 a 1.0001,1.0001 0 0 0 1,-1 1.0001,1.0001 0 0 0 1,1 l 1,0 a 1.0001,1.0001 0 0 0 1,-1 1.0001,1.0001 0 0 0 0.707031,-0.292969 L 12.414062,12 13,12 a 1.0001,1.0001 0 0 0 1,-1 L 14,9 A 1.0001,1.0001 0 0 0 13,8 L 12.98828,8 C 12.990166,7.953267 13.043574,6.8170161 12.449219,5.59375 11.821938,4.3027137 10.312122,3 8,3 Z"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.15;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
id="path22" />
|
||||
<path
|
||||
style="display:inline;opacity:0.3;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 8,4 C 6.0384698,4 4.9369018,5.0315454 4.4511719,6.03125 3.965442,7.0309546 4,8.0136719 4,8.0136719 L 4,9 3,9 l 0,2 1,0 1,1 1,0 0,1 1,0 L 7,8 6,8 6,9 5,9 5,8 A 0.50005,0.50005 0 0 0 5,7.986328 c 0,0 -0.014939,-0.7672827 0.3496094,-1.5175781 C 5.714158,5.7184547 6.3626442,5 8,5 9.6373558,5 10.285842,5.7184546 10.650391,6.46875 11.014939,7.2190454 11,7.9863281 11,7.9863281 A 0.50005,0.50005 0 0 0 11,8 l 0,1 -1,0 0,-1 -1,0 0,5 1,0 0,-1 1,0 1,-1 1,0 0,-2 -1,0 0,-0.9863281 c 0,0 0.03456,-0.9827173 -0.451172,-1.9824219 C 11.063098,5.0315454 9.9615302,4 8,4 Z"
|
||||
id="path24" />
|
||||
<path
|
||||
d="m 3,8 0,2 1,0 1,1 1,0 0,1 1,0 0,-5 -1,0 8.01e-5,1 z"
|
||||
style="display:inline;opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path30" />
|
||||
<path
|
||||
d="m 4.5,8 0,-1 C 4.5,7 4.401114,3.5 8,3.5 11.598886,3.5 11.5,7 11.5,7 l 0,1"
|
||||
style="display:inline;opacity:1;fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path32" />
|
||||
<path
|
||||
style="display:inline;opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 13,8 0,2 -1,0 -1,1 -1,0 0,1 -1,0 0,-5 1,0 -8e-5,1 z"
|
||||
id="path34" />
|
||||
<path
|
||||
d="M 8,3 C 6.0384698,3 4.9369018,4.0315454 4.4511719,5.03125 3.965442,6.0309546 4,7.0136719 4,7.0136719 L 4,8 3,8 l 0,2 1,0 1,1 1,0 0,1 1,0 L 7,7 6,7 6,8 5,8 5,7 A 0.50005,0.50005 0 0 0 5,6.986328 c 0,0 -0.014939,-0.7672827 0.3496094,-1.5175781 C 5.714158,4.7184547 6.3626442,4 8,4 9.6373558,4 10.285842,4.7184546 10.650391,5.46875 11.014939,6.2190454 11,6.9863281 11,6.9863281 A 0.50005,0.50005 0 0 0 11,7 l 0,1 -1,0 0,-1 -1,0 0,5 1,0 0,-1 1,0 1,-1 1,0 0,-2 -1,0 0,-0.9863281 c 0,0 0.03456,-0.9827173 -0.451172,-1.9824219 C 11.063098,4.0315454 9.9615302,3 8,3 Z"
|
||||
style="display:inline;opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path36" />
|
||||
<rect
|
||||
ry="0.5"
|
||||
y="1.5"
|
||||
x="1.5"
|
||||
height="13"
|
||||
width="13"
|
||||
id="rect829"
|
||||
style="fill:none;fill-opacity:1;stroke:url(#linearGradient837);stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</svg>
|
After Width: | Height: | Size: 8.7 KiB |
BIN
public/static/icon_192.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
public/static/icon_24.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
354
public/static/icon_24.svg
Normal file
@ -0,0 +1,354 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="24"
|
||||
height="24"
|
||||
version="1.1"
|
||||
viewBox="0 0 24 24"
|
||||
id="svg89"
|
||||
sodipodi:docname="icon_24.svg"
|
||||
inkscape:version="0.92.4 5da689c313, 2019-01-14"
|
||||
inkscape:export-filename="/home/djoma/Projekte/artemanufrij/app/webplay-client/public/static/icon_24.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<metadata
|
||||
id="metadata93">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1692"
|
||||
inkscape:window-height="1062"
|
||||
id="namedview91"
|
||||
showgrid="false"
|
||||
inkscape:zoom="13.906433"
|
||||
inkscape:cx="-8.73444"
|
||||
inkscape:cy="15.866314"
|
||||
inkscape:window-x="367"
|
||||
inkscape:window-y="143"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg89" />
|
||||
<defs
|
||||
id="defs39">
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient867">
|
||||
<stop
|
||||
style="stop-color:#535c65;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop863" />
|
||||
<stop
|
||||
style="stop-color:#31363b;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop865" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient859">
|
||||
<stop
|
||||
style="stop-color:#fdfdfd;stop-opacity:0.25098041"
|
||||
offset="0"
|
||||
id="stop855" />
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:0.25098041"
|
||||
offset="1"
|
||||
id="stop857" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
id="radialGradient4214"
|
||||
cx="24"
|
||||
cy="-4.5"
|
||||
r="20"
|
||||
fx="24"
|
||||
fy="-4.5"
|
||||
gradientTransform="matrix(1,0,0,0.7,8.7243652e-7,8.15)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient4208" />
|
||||
<linearGradient
|
||||
id="linearGradient4208">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0.25098041"
|
||||
offset="0"
|
||||
id="stop3" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0"
|
||||
offset="1"
|
||||
id="stop5" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient5397"
|
||||
x1="24"
|
||||
x2="24"
|
||||
y1="5"
|
||||
y2="43"
|
||||
gradientTransform="matrix(0.45945947,0,0,0.45945947,0.97296959,0.972977)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient3924-64" />
|
||||
<linearGradient
|
||||
id="linearGradient3924-64">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop9" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0.23529412"
|
||||
offset=".063"
|
||||
id="stop11" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0.15686275"
|
||||
offset=".951"
|
||||
id="stop13" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0.39215687"
|
||||
offset="1"
|
||||
id="stop15" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
id="radialGradient3082-6"
|
||||
cx="4.993"
|
||||
cy="43.5"
|
||||
r="2.5"
|
||||
fx="4.993"
|
||||
fy="43.5"
|
||||
gradientTransform="matrix(2.003784,0,0,1.4,27.98813,-17.4)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient3688-166-749-9" />
|
||||
<linearGradient
|
||||
id="linearGradient3688-166-749-9">
|
||||
<stop
|
||||
style="stop-color:#181818;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop19" />
|
||||
<stop
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
offset="1"
|
||||
id="stop21" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
id="radialGradient3084-4"
|
||||
cx="4.993"
|
||||
cy="43.5"
|
||||
r="2.5"
|
||||
fx="4.993"
|
||||
fy="43.5"
|
||||
gradientTransform="matrix(2.003784,0,0,1.4,-20.01187,-104.4)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient3688-166-749-9" />
|
||||
<linearGradient
|
||||
id="linearGradient3086-8"
|
||||
x1="25.058"
|
||||
x2="25.058"
|
||||
y1="47.028"
|
||||
y2="39.999"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient3702-501-757-1" />
|
||||
<linearGradient
|
||||
id="linearGradient3702-501-757-1">
|
||||
<stop
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
offset="0"
|
||||
id="stop26" />
|
||||
<stop
|
||||
style="stop-color:#181818;stop-opacity:1"
|
||||
offset=".5"
|
||||
id="stop28" />
|
||||
<stop
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
offset="1"
|
||||
id="stop30" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient4256">
|
||||
<stop
|
||||
style="stop-color:#1777ba;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop33" />
|
||||
<stop
|
||||
style="stop-color:#5cb7f6;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop35" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient4234"
|
||||
x1="13"
|
||||
x2="13"
|
||||
y1="21"
|
||||
y2="3"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient4256" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient859"
|
||||
id="linearGradient861"
|
||||
x1="11"
|
||||
y1="2"
|
||||
x2="11"
|
||||
y2="23"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient867"
|
||||
id="radialGradient869"
|
||||
cx="12.071909"
|
||||
cy="2.004626"
|
||||
fx="12.071909"
|
||||
fy="2.004626"
|
||||
r="10"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0,1.9918839,-2.337425,0,16.757573,-22.041215)" />
|
||||
</defs>
|
||||
<rect
|
||||
style="fill:url(#radialGradient869);fill-opacity:1;stroke:none;stroke-width:1.86977518;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect851"
|
||||
width="20"
|
||||
height="20"
|
||||
x="2"
|
||||
y="2"
|
||||
ry="1" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.15;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="M 12 5 C 8.5412537 5 6.3644631 6.8042203 5.5410156 8.703125 C 4.8908809 10.202363 4.9589798 11.439099 5.0039062 11.919922 A 1.0001 1.0001 0 0 0 5.0039062 11.923828 A 1.0001 1.0001 0 0 0 5 12 L 5 12.025391 A 1.0001 1.0001 0 0 0 5 12.044922 L 5 12.105469 C 4.4522089 12.236857 4.0000582 12.668041 4 13.25 L 4 15.75 C 4.0000678 16.428436 4.5715647 16.999931 5.25 17 L 5.4785156 17 L 5.6328125 17.306641 C 5.8421982 17.727594 6.2759436 17.997735 6.7460938 18 A 1.0001 1.0001 0 0 0 6.75 18 L 7.9785156 18 L 8.1328125 18.306641 C 8.3421984 18.727595 8.7759433 18.997735 9.2460938 19 A 1.0001 1.0001 0 0 0 9.25 19 L 9.75 19 C 10.428435 18.999931 10.999932 18.428436 11 17.75 L 11 12.25 C 10.999932 11.571564 10.428435 11.000069 9.75 11 L 9.25 11 A 1.0001 1.0001 0 0 0 9.2460938 11 C 9.1790787 11.000323 9.1528788 11.088739 9.0878906 11.099609 C 9.1029183 10.808399 9.0532567 10.660494 9.2109375 10.296875 C 9.3784115 9.9106681 9.5362927 9.6565658 9.8867188 9.4335938 C 10.237145 9.2106215 10.85473 9 12 9 C 13.145273 9 13.762856 9.2106217 14.113281 9.4335938 C 14.463706 9.6565656 14.621587 9.9106654 14.789062 10.296875 C 14.946744 10.660493 14.897085 10.8084 14.912109 11.099609 C 14.847121 11.088742 14.820922 11.000323 14.753906 11 A 1.0001 1.0001 0 0 0 14.75 11 L 14.25 11 C 13.571563 11.000069 13.000069 11.571563 13 12.25 L 13 17.75 C 13.000069 18.428437 13.571563 18.999931 14.25 19 L 14.75 19 A 1.0001 1.0001 0 0 0 14.753906 19 C 15.224062 18.9977 15.657789 18.727627 15.867188 18.306641 L 16.021484 18 L 17.25 18 A 1.0001 1.0001 0 0 0 17.253906 18 C 17.724062 17.9977 18.157789 17.727627 18.367188 17.306641 L 18.521484 17 L 18.75 17 C 19.428437 16.999931 19.999931 16.428437 20 15.75 L 20 13.25 C 19.999941 12.66804 19.547791 12.236857 19 12.105469 L 19 12.044922 L 19 12 A 1.0001 1.0001 0 0 0 18.996094 11.925781 C 19.041063 11.448344 19.11085 10.20636 18.458984 8.703125 C 17.635529 6.8042307 15.458751 5 12 5 z"
|
||||
id="path53" />
|
||||
<rect
|
||||
width="2"
|
||||
height="1"
|
||||
x="-7"
|
||||
y="17"
|
||||
rx="0"
|
||||
ry="0"
|
||||
transform="scale(-1,1)"
|
||||
style="display:inline;opacity:0.65;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect55" />
|
||||
<rect
|
||||
style="display:inline;opacity:0.65;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="2"
|
||||
height="1"
|
||||
x="17"
|
||||
y="17"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect57" />
|
||||
<rect
|
||||
style="display:inline;opacity:0.9;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="2"
|
||||
height="1"
|
||||
x="-10"
|
||||
y="19"
|
||||
rx="0"
|
||||
ry="0"
|
||||
transform="scale(-1,1)"
|
||||
id="rect59" />
|
||||
<rect
|
||||
style="display:inline;opacity:0.9;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="2"
|
||||
height="1"
|
||||
x="-7"
|
||||
y="19"
|
||||
rx="0"
|
||||
ry="0"
|
||||
transform="scale(-1,1)"
|
||||
id="rect61" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.3;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="M 12,6 C 8.8739383,6 7.1274777,7.5599887 6.4589844,9.1015625 5.8466206,10.513699 5.9736939,11.825328 6,12.044922 L 6,12 l 0.00781,0.130859 c 0,0 -0.0054,-0.06581 -0.00781,-0.08594 L 6,13 5.25,13 C 5.1119345,13.000014 5.0000138,13.111935 5,13.25 l 0,2.5 c 1.38e-5,0.138065 0.1119345,0.249986 0.25,0.25 l 0.8457031,0 0.4316407,0.861328 C 6.5694101,16.945899 6.6555455,16.999545 6.75,17 l 1.8457031,0 0.4316407,0.861328 C 9.0694101,17.945899 9.1555456,17.999545 9.25,18 l 0.5,0 c 0.1380655,-1.4e-5 0.2499862,-0.111935 0.25,-0.25 l 0,-5.5 C 9.9999862,12.111935 9.8880655,12.000014 9.75,12 l -0.5,0 c -0.094454,4.55e-4 -0.1805899,0.0541 -0.2226562,0.138672 L 8.5957031,13 8,13 8,12 a 1.0001,1.0001 0 0 0 -0.00781,-0.130859 c 0,0 -0.114834,-1.012277 0.3007813,-1.9707035 C 8.7085814,8.9400113 9.4624957,8 12,8 c 2.537511,0 3.291418,0.9400123 3.707031,1.8984375 0.415614,0.9584255 0.300781,1.9707035 0.300781,1.9707035 A 1.0001,1.0001 0 0 0 16,12 l 0,1 -0.595703,0 -0.431641,-0.861328 C 14.930589,12.054101 14.844454,12.000455 14.75,12 l -0.5,0 c -0.138065,1.4e-5 -0.249986,0.111935 -0.25,0.25 l 0,5.5 c 1.4e-5,0.138065 0.111935,0.249986 0.25,0.25 l 0.5,0 c 0.09445,-4.55e-4 0.18059,-0.0541 0.222656,-0.138672 L 15.404297,17 17.25,17 c 0.09445,-4.55e-4 0.18059,-0.0541 0.222656,-0.138672 L 17.904297,16 18.75,16 c 0.138065,-1.4e-5 0.249986,-0.111935 0.25,-0.25 l 0,-2.5 C 18.999986,13.111935 18.888065,13.000014 18.75,13 L 18,13 18,12.044922 c -0.0024,0.02013 -0.0078,0.08594 -0.0078,0.08594 L 18,12 l 0,0.04492 C 18.026306,11.825328 18.153378,10.5137 17.541016,9.1015625 16.872512,7.5599877 15.126067,6 12,6 Z"
|
||||
id="path63" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 9.2501484,11 c -0.094454,4.55e-4 -0.1805898,0.0541 -0.2226561,0.138672 L 8.5958515,12 5.25,12 C 5.1119345,12.000014 5.0000138,12.111935 5,12.25 l 0,2.5 c 1.38e-5,0.138065 0.1119345,0.249986 0.25,0.25 l 0.8457031,0 0.4316407,0.861328 C 6.5694101,15.945899 6.6555455,15.999545 6.75,16 l 1.8458515,0 0.4316408,0.861328 C 9.0695586,16.945899 9.155694,16.999545 9.2501484,17 L 9.75,17 c 0.1380655,-1.4e-5 0.2499862,-0.111935 0.25,-0.25 l 0,-5.5 C 9.9999862,11.111935 9.8880655,11.000014 9.75,11 Z"
|
||||
id="path65" />
|
||||
<path
|
||||
d="M 7,12.23935 7,11 c 0,0 -0.663566,-5 5,-5 5.663578,0 5,5 5,5 l 0,1.23935"
|
||||
style="display:inline;opacity:1;fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path67" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 14.25,11 c -0.138065,1.4e-5 -0.249986,0.111935 -0.25,0.25 l 0,5.5 c 1.4e-5,0.138065 0.111935,0.249986 0.25,0.25 l 0.499852,0 c 0.09445,-4.55e-4 0.18059,-0.0541 0.222656,-0.138672 L 15.404149,16 17.25,16 c 0.09445,-4.55e-4 0.18059,-0.0541 0.222656,-0.138672 L 17.904297,15 18.75,15 c 0.138065,-1.4e-5 0.249986,-0.111935 0.25,-0.25 l 0,-2.5 C 18.999986,12.111935 18.888065,12.000014 18.75,12 l -3.345851,0 -0.431641,-0.861328 C 14.930441,11.054101 14.844306,11.000455 14.749852,11 Z"
|
||||
id="path69" />
|
||||
<rect
|
||||
style="display:inline;opacity:0.9;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="2"
|
||||
height="1"
|
||||
x="11"
|
||||
y="19"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect71" />
|
||||
<rect
|
||||
width="2"
|
||||
height="1"
|
||||
x="14"
|
||||
y="19"
|
||||
rx="0"
|
||||
ry="0"
|
||||
style="display:inline;opacity:0.9;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect73" />
|
||||
<rect
|
||||
width="2"
|
||||
height="1"
|
||||
x="17"
|
||||
y="19"
|
||||
rx="0"
|
||||
ry="0"
|
||||
style="display:inline;opacity:0.9;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect75" />
|
||||
<rect
|
||||
width="2"
|
||||
height="1"
|
||||
x="11"
|
||||
y="17"
|
||||
rx="0"
|
||||
ry="0"
|
||||
style="display:inline;opacity:0.65;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect77" />
|
||||
<rect
|
||||
style="display:inline;opacity:0.4;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="2"
|
||||
height="1"
|
||||
x="11"
|
||||
y="15"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect79" />
|
||||
<g
|
||||
style="display:none"
|
||||
transform="translate(0,-24)"
|
||||
id="g87">
|
||||
<rect
|
||||
style="fill:url(#radialGradient4214);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
width="40"
|
||||
height="14"
|
||||
x="4"
|
||||
y="5"
|
||||
ry="2"
|
||||
id="rect85" />
|
||||
</g>
|
||||
<rect
|
||||
ry="0.5"
|
||||
y="2.5"
|
||||
x="2.5"
|
||||
height="19"
|
||||
width="19"
|
||||
id="rect853"
|
||||
style="fill:none;fill-opacity:1;stroke:url(#linearGradient861);stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</svg>
|
After Width: | Height: | Size: 19 KiB |
BIN
public/static/icon_32.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
373
public/static/icon_32.svg
Normal file
@ -0,0 +1,373 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="32"
|
||||
height="32"
|
||||
version="1.1"
|
||||
id="svg94"
|
||||
sodipodi:docname="icon_32.svg"
|
||||
inkscape:version="0.92.4 5da689c313, 2019-01-14"
|
||||
inkscape:export-filename="/home/djoma/Projekte/artemanufrij/app/webplay-client/public/static/icon_32.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<metadata
|
||||
id="metadata98">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1729"
|
||||
inkscape:window-height="1024"
|
||||
id="namedview96"
|
||||
showgrid="false"
|
||||
inkscape:zoom="10.429825"
|
||||
inkscape:cx="-9.0237505"
|
||||
inkscape:cy="-9.5963558"
|
||||
inkscape:window-x="124"
|
||||
inkscape:window-y="112"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg94" />
|
||||
<defs
|
||||
id="defs38">
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient949">
|
||||
<stop
|
||||
style="stop-color:#545c64;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop945" />
|
||||
<stop
|
||||
style="stop-color:#31363b;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop947" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient941">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0.25098041"
|
||||
offset="0"
|
||||
id="stop937" />
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:0.25098041"
|
||||
offset="1"
|
||||
id="stop939" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient4161"
|
||||
x1="24"
|
||||
x2="24"
|
||||
y1="5"
|
||||
y2="43"
|
||||
gradientTransform="matrix(0.67567568,0,0,0.67567568,-0.21621703,-0.21620627)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient3924-0" />
|
||||
<linearGradient
|
||||
id="linearGradient3924-0">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#ffffff;stop-opacity:1"
|
||||
id="stop3" />
|
||||
<stop
|
||||
offset=".063"
|
||||
style="stop-color:#ffffff;stop-opacity:0.23529412"
|
||||
id="stop5" />
|
||||
<stop
|
||||
offset=".951"
|
||||
style="stop-color:#ffffff;stop-opacity:0.15686275"
|
||||
id="stop7" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#ffffff;stop-opacity:0.39215687"
|
||||
id="stop9" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
id="radialGradient2976"
|
||||
cx="4.993"
|
||||
cy="43.5"
|
||||
r="2.5"
|
||||
fx="4.993"
|
||||
fy="43.5"
|
||||
gradientTransform="matrix(2.003784,0,0,1.4,27.98813,-17.4)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient3688-166-749-6" />
|
||||
<linearGradient
|
||||
id="linearGradient3688-166-749-6">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#181818;stop-opacity:1"
|
||||
id="stop13" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
id="stop15" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
id="radialGradient2978"
|
||||
cx="4.993"
|
||||
cy="43.5"
|
||||
r="2.5"
|
||||
fx="4.993"
|
||||
fy="43.5"
|
||||
gradientTransform="matrix(2.003784,0,0,1.4,-20.01187,-104.4)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient3688-464-309-7" />
|
||||
<linearGradient
|
||||
id="linearGradient3688-464-309-7">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#181818;stop-opacity:1"
|
||||
id="stop19" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
id="stop21" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient2980"
|
||||
x1="25.058"
|
||||
x2="25.058"
|
||||
y1="47.028"
|
||||
y2="39.999"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient3702-501-757-3" />
|
||||
<linearGradient
|
||||
id="linearGradient3702-501-757-3">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
id="stop25" />
|
||||
<stop
|
||||
offset=".5"
|
||||
style="stop-color:#181818;stop-opacity:1"
|
||||
id="stop27" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
id="stop29" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient4256">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#1777ba;stop-opacity:1"
|
||||
id="stop32" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#5cb7f6;stop-opacity:1"
|
||||
id="stop34" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient4248"
|
||||
x1="16"
|
||||
x2="16"
|
||||
y1="29"
|
||||
y2="3"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient4256" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient941"
|
||||
id="linearGradient943"
|
||||
x1="14"
|
||||
y1="2"
|
||||
x2="14"
|
||||
y2="30"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(0.5)" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient949"
|
||||
id="radialGradient951"
|
||||
cx="8"
|
||||
cy="16"
|
||||
fx="8"
|
||||
fy="16"
|
||||
r="14"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0,1.9935897,-2.1298809,0,50.078095,-13.484182)" />
|
||||
</defs>
|
||||
<rect
|
||||
style="fill:url(#radialGradient951);fill-opacity:1;stroke:none;stroke-width:2.17991281;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect925"
|
||||
width="28"
|
||||
height="28"
|
||||
x="2"
|
||||
y="2"
|
||||
ry="2" />
|
||||
<path
|
||||
d="M 16 5 C 11.560863 5 8.7693012 7.2360203 7.4296875 9.4726562 C 6.1060542 11.682611 6.0039063 13.894531 6.0039062 13.894531 C 6.0020435 13.927059 6.0002716 13.959606 6 13.992188 A 1.0001 1.0001 0 0 0 6 14 L 6 16.208984 C 5.4481455 16.432277 5.0000623 16.876571 5 17.5 L 5 19.5 C 5.0000816 20.3165 5.6834996 20.999918 6.5 21 L 6.5742188 21 L 7.1582031 22.167969 C 7.4105832 22.675344 7.933321 22.999486 8.5 23 L 9.8789062 23 L 10.439453 23.560547 C 10.720431 23.841561 11.102612 23.999924 11.5 24 L 12.5 24 C 13.3165 23.999918 13.999918 23.3165 14 22.5 L 14 15.5 C 13.999918 14.6835 13.3165 14.000082 12.5 14 L 11.5 14 C 10.933321 14.000513 10.410584 14.324656 10.158203 14.832031 L 10 15.146484 L 10 14.099609 C 9.9992628 14.112029 10.08956 12.812634 10.859375 11.527344 C 11.65569 10.197806 12.783237 9 16 9 C 19.216764 9 20.34431 10.197806 21.140625 11.527344 C 21.910439 12.812635 22.001422 14.123011 22 14.099609 L 22 15.146484 L 21.841797 14.832031 C 21.589416 14.324656 21.066679 14.000513 20.5 14 L 19.5 14 C 18.6835 14.000082 18.000082 14.6835 18 15.5 L 18 22.5 C 18.000082 23.3165 18.6835 23.999918 19.5 24 L 20.5 24 C 20.897388 23.999924 21.279569 23.841561 21.560547 23.560547 L 22.121094 23 L 23.5 23 C 24.066679 22.999487 24.589416 22.675344 24.841797 22.167969 L 25.425781 21 L 25.5 21 C 26.3165 20.999918 26.999918 20.3165 27 19.5 L 27 17.5 C 26.999938 16.876571 26.551855 16.432277 26 16.208984 L 26 14 A 1.0001 1.0001 0 0 0 26 13.990234 C 25.999707 13.958304 25.997894 13.926411 25.996094 13.894531 C 25.996094 13.894531 25.893947 11.682611 24.570312 9.4726562 C 23.230808 7.2359838 20.439137 5 16 5 z"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.15;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
id="path52" />
|
||||
<path
|
||||
d="M 16,6 C 11.886169,6 9.4723841,8.0073783 8.2871094,9.9863281 7.1018346,11.965278 7.0019531,13.945315 7.0019531,13.945312 A 1.0001,1.0001 0 0 0 7,14 l 0,3 -0.5,0 A 0.50005,0.50005 0 0 0 6,17.5 l 0,2 A 0.50005,0.50005 0 0 0 6.5,20 l 0.6914062,0 0.8613282,1.722656 A 0.50005,0.50005 0 0 0 8.5,22 l 1.792969,0 0.853515,0.853516 A 0.50005,0.50005 0 0 0 11.5,23 l 1,0 A 0.50005,0.50005 0 0 0 13,22.5 l 0,-7 A 0.50005,0.50005 0 0 0 12.5,15 l -1,0 a 0.50005,0.50005 0 0 0 -0.447266,0.277344 L 10.191406,17 9,17 9,14.042969 C 9.00117,14.024379 9.096555,12.525336 10.001953,11.013672 10.912973,9.4926217 12.499009,8 16,8 c 3.500992,0 5.087027,1.4926217 5.998047,3.013672 0.905398,1.511664 1.000787,3.01071 1.001953,3.029297 L 23,17 21.808594,17 20.947266,15.277344 A 0.50005,0.50005 0 0 0 20.5,15 l -1,0 A 0.50005,0.50005 0 0 0 19,15.5 l 0,7 a 0.50005,0.50005 0 0 0 0.5,0.5 l 1,0 a 0.50005,0.50005 0 0 0 0.353516,-0.146484 L 21.707031,22 23.5,22 a 0.50005,0.50005 0 0 0 0.447266,-0.277344 L 24.808594,20 25.5,20 A 0.50005,0.50005 0 0 0 26,19.5 l 0,-2 A 0.50005,0.50005 0 0 0 25.5,17 l -0.5,0 0,-3 a 1.0001,1.0001 0 0 0 -0.002,-0.05469 c 0,0 -0.09988,-1.980034 -1.285156,-3.9589839 C 22.52771,8.0073823 20.113831,6 16,6 Z"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.3;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
id="path54" />
|
||||
<rect
|
||||
style="opacity:0.9;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="2"
|
||||
height="1"
|
||||
x="5"
|
||||
y="26"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect60" />
|
||||
<rect
|
||||
width="2"
|
||||
height="1"
|
||||
x="8"
|
||||
y="26"
|
||||
rx="0"
|
||||
ry="0"
|
||||
style="opacity:0.9;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect62" />
|
||||
<rect
|
||||
style="opacity:0.9;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="3"
|
||||
height="1"
|
||||
x="11"
|
||||
y="26"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect64" />
|
||||
<rect
|
||||
width="2"
|
||||
height="1"
|
||||
x="15"
|
||||
y="26"
|
||||
rx="0"
|
||||
ry="0"
|
||||
style="opacity:0.9;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect66" />
|
||||
<rect
|
||||
style="opacity:0.9;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="3"
|
||||
height="1"
|
||||
x="18"
|
||||
y="26"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect68" />
|
||||
<rect
|
||||
width="2"
|
||||
height="1"
|
||||
x="22"
|
||||
y="26"
|
||||
rx="0"
|
||||
ry="0"
|
||||
style="opacity:0.9;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect70" />
|
||||
<rect
|
||||
width="2"
|
||||
height="1"
|
||||
x="5"
|
||||
y="24"
|
||||
rx="0"
|
||||
ry="0"
|
||||
style="opacity:0.65;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect72" />
|
||||
<rect
|
||||
width="3"
|
||||
height="1"
|
||||
x="11"
|
||||
y="24"
|
||||
rx="0"
|
||||
ry="0"
|
||||
style="opacity:0.65;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect74" />
|
||||
<rect
|
||||
style="opacity:0.65;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="2"
|
||||
height="1"
|
||||
x="15"
|
||||
y="24"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect76" />
|
||||
<rect
|
||||
width="3"
|
||||
height="1"
|
||||
x="18"
|
||||
y="24"
|
||||
rx="0"
|
||||
ry="0"
|
||||
style="opacity:0.65;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect78" />
|
||||
<rect
|
||||
width="2"
|
||||
height="1"
|
||||
x="15"
|
||||
y="22"
|
||||
rx="0"
|
||||
ry="0"
|
||||
style="opacity:0.4;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect80" />
|
||||
<rect
|
||||
style="opacity:0.9;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="2"
|
||||
height="1"
|
||||
x="25"
|
||||
y="26"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect82" />
|
||||
<rect
|
||||
width="2"
|
||||
height="1"
|
||||
x="25"
|
||||
y="24"
|
||||
rx="0"
|
||||
ry="0"
|
||||
style="opacity:0.65;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect84" />
|
||||
<rect
|
||||
style="opacity:0.4;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="2"
|
||||
height="1"
|
||||
x="25"
|
||||
y="22"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect86" />
|
||||
<path
|
||||
d="m 6.5,16.500017 0,2 1,0 1,2 2,0 1,1 1,0 0,-7 -1,0 -1,2 z"
|
||||
style="display:inline;opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path88" />
|
||||
<path
|
||||
d="m 8,16.000017 0,-3 c 0,0 0.3851775,-6.9999996 8,-6.9999996 7.614823,0 8,6.9999996 8,6.9999996 l 0,3"
|
||||
style="display:inline;opacity:1;fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path90" />
|
||||
<path
|
||||
style="display:inline;opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 25.5,16.500017 0,2 -1,0 -1,2 -2,0 -1,1 -1,0 0,-7 1,0 1,2 z"
|
||||
id="path92" />
|
||||
<rect
|
||||
ry="1.5"
|
||||
y="2.5"
|
||||
x="2.5"
|
||||
height="27"
|
||||
width="27"
|
||||
id="rect927"
|
||||
style="fill:none;fill-opacity:1;stroke:url(#linearGradient943);stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</svg>
|
After Width: | Height: | Size: 16 KiB |
BIN
public/static/icon_48.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
373
public/static/icon_48.svg
Normal file
@ -0,0 +1,373 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="48"
|
||||
height="48"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
inkscape:version="0.92.4 5da689c313, 2019-01-14"
|
||||
sodipodi:docname="icon_48.svg"
|
||||
inkscape:export-filename="/home/djoma/Projekte/artemanufrij/app/webplay-client/public/static/icon_48.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<metadata
|
||||
id="metadata96">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1459"
|
||||
inkscape:window-height="984"
|
||||
id="namedview94"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.9166667"
|
||||
inkscape:cx="5.610646"
|
||||
inkscape:cy="-19.856276"
|
||||
inkscape:window-x="171"
|
||||
inkscape:window-y="227"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" />
|
||||
<defs
|
||||
id="defs4">
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient877">
|
||||
<stop
|
||||
style="stop-color:#545c64;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop873" />
|
||||
<stop
|
||||
style="stop-color:#31363b;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop875" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient869">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0.25098041"
|
||||
offset="0"
|
||||
id="stop865" />
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:0.5"
|
||||
offset="1"
|
||||
id="stop867" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3058"
|
||||
x1="24"
|
||||
x2="24"
|
||||
y1="5"
|
||||
y2="43"
|
||||
gradientTransform="translate(4e-6,1.000006)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient3924-1" />
|
||||
<linearGradient
|
||||
id="linearGradient3924-1">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#ffffff;stop-opacity:1"
|
||||
id="stop8" />
|
||||
<stop
|
||||
offset=".063"
|
||||
style="stop-color:#ffffff;stop-opacity:0.23529412"
|
||||
id="stop10" />
|
||||
<stop
|
||||
offset=".951"
|
||||
style="stop-color:#ffffff;stop-opacity:0.15686275"
|
||||
id="stop12" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#ffffff;stop-opacity:0.39215687"
|
||||
id="stop14" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
id="radialGradient3013"
|
||||
cx="4.993"
|
||||
cy="43.5"
|
||||
r="2.5"
|
||||
fx="4.993"
|
||||
fy="43.5"
|
||||
gradientTransform="matrix(2.4045409,0,0,0.89999994,27.985756,4.600003)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient3688-166-749" />
|
||||
<linearGradient
|
||||
id="linearGradient3688-166-749">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#181818;stop-opacity:1"
|
||||
id="stop18" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
id="stop20" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
id="radialGradient3015"
|
||||
cx="4.993"
|
||||
cy="43.5"
|
||||
r="2.5"
|
||||
fx="4.993"
|
||||
fy="43.5"
|
||||
gradientTransform="matrix(2.4045409,0,0,0.89999994,-20.014244,-82.899992)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient3688-464-309" />
|
||||
<linearGradient
|
||||
id="linearGradient3688-464-309">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#181818;stop-opacity:1"
|
||||
id="stop24" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
id="stop26" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3702-501-757">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
id="stop29" />
|
||||
<stop
|
||||
offset=".5"
|
||||
style="stop-color:#181818;stop-opacity:1"
|
||||
id="stop31" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
id="stop33" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient6647"
|
||||
x1="25.058"
|
||||
x2="25.058"
|
||||
y1="47.028"
|
||||
y2="39.999"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient3702-501-757"
|
||||
gradientTransform="matrix(1.1428571,0,0,0.6428571,-3.4285711,15.785724)" />
|
||||
<linearGradient
|
||||
id="linearGradient4256">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#1777ba;stop-opacity:1"
|
||||
id="stop37" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#5cb7f6;stop-opacity:1"
|
||||
id="stop39" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient4254"
|
||||
x1="24"
|
||||
x2="24"
|
||||
y1="44"
|
||||
y2="5.972"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient4256" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient869"
|
||||
id="linearGradient871"
|
||||
x1="25"
|
||||
y1="3"
|
||||
x2="25"
|
||||
y2="45"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient877"
|
||||
id="radialGradient879"
|
||||
cx="24"
|
||||
cy="3.0744331"
|
||||
fx="24"
|
||||
fy="3.0744331"
|
||||
r="21"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0,1.9467312,-2.2881356,0,31.03472,-43.647117)" />
|
||||
</defs>
|
||||
<rect
|
||||
style="fill:url(#radialGradient879);fill-opacity:1;stroke:none;stroke-width:1.99050581;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect853"
|
||||
width="42"
|
||||
height="42"
|
||||
x="3"
|
||||
y="3"
|
||||
ry="2" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.15;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="M 24,9 C 17.872945,9 14.350715,12.036316 12.691406,15.021484 11.032098,18.006653 11,20.974609 11,20.974609 A 1.50015,1.50015 0 0 0 11,21 l 0,4 -1.5,0 A 0.50005,0.50005 0 0 0 9,25.5 l 0,5 A 0.50005,0.50005 0 0 0 9.5,31 l 1.691406,0 0.861328,1.722656 A 0.50005,0.50005 0 0 0 12.5,33 l 2.691406,0 0.861328,1.722656 A 0.50005,0.50005 0 0 0 16.5,35 l 2,0 A 0.50005,0.50005 0 0 0 19,34.5 l 0,-11 A 0.50005,0.50005 0 0 0 18.5,23 l -2,0 a 0.50005,0.50005 0 0 0 -0.447266,0.277344 L 15.191406,25 14,25 14,21.025391 c 0,0 0.05359,-2.282044 1.3125,-4.546875 C 16.571408,14.213684 18.799922,12 24,12 c 5.200091,0 7.428595,2.213686 8.6875,4.478516 C 33.946405,18.743345 34,21.025391 34,21.025391 L 34,25 32.808594,25 31.947266,23.277344 A 0.50005,0.50005 0 0 0 31.5,23 l -2,0 A 0.50005,0.50005 0 0 0 29,23.5 l 0,11 a 0.50005,0.50005 0 0 0 0.5,0.5 l 2,0 a 0.50005,0.50005 0 0 0 0.447266,-0.277344 L 32.808594,33 35.5,33 a 0.50005,0.50005 0 0 0 0.447266,-0.277344 L 36.808594,31 38.5,31 A 0.50005,0.50005 0 0 0 39,30.5 l 0,-5 A 0.50005,0.50005 0 0 0 38.5,25 l -1.5,0 0,-4 a 1.50015,1.50015 0 0 0 0,-0.02539 c 0,0 -0.0321,-2.967955 -1.691406,-5.953125 C 33.649288,12.036312 30.127066,9 24,9 Z"
|
||||
id="path52" />
|
||||
<path
|
||||
d="M 24,9 C 17.872945,9 14.350715,12.036316 12.691406,15.021484 11.032098,18.006653 11,20.974609 11,20.974609 A 1.50015,1.50015 0 0 0 11,21 l 0,4 -1.5,0 A 0.50005,0.50005 0 0 0 9,25.5 l 0,5 A 0.50005,0.50005 0 0 0 9.5,31 l 1.691406,0 0.861328,1.722656 A 0.50005,0.50005 0 0 0 12.5,33 l 2.691406,0 0.861328,1.722656 A 0.50005,0.50005 0 0 0 16.5,35 l 2,0 A 0.50005,0.50005 0 0 0 19,34.5 l 0,-11 A 0.50005,0.50005 0 0 0 18.5,23 l -2,0 a 0.50005,0.50005 0 0 0 -0.447266,0.277344 L 15.191406,25 14,25 14,21.025391 c 0,0 0.05359,-2.282044 1.3125,-4.546875 C 16.571408,14.213684 18.799922,12 24,12 c 5.200091,0 7.428595,2.213686 8.6875,4.478516 C 33.946405,18.743345 34,21.025391 34,21.025391 L 34,25 32.808594,25 31.947266,23.277344 A 0.50005,0.50005 0 0 0 31.5,23 l -2,0 A 0.50005,0.50005 0 0 0 29,23.5 l 0,11 a 0.50005,0.50005 0 0 0 0.5,0.5 l 2,0 a 0.50005,0.50005 0 0 0 0.447266,-0.277344 L 32.808594,33 35.5,33 a 0.50005,0.50005 0 0 0 0.447266,-0.277344 L 36.808594,31 38.5,31 A 0.50005,0.50005 0 0 0 39,30.5 l 0,-5 A 0.50005,0.50005 0 0 0 38.5,25 l -1.5,0 0,-4 a 1.50015,1.50015 0 0 0 0,-0.02539 c 0,0 -0.0321,-2.967955 -1.691406,-5.953125 C 33.649288,12.036312 30.127066,9 24,9 Z"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.3;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
id="path54" />
|
||||
<path
|
||||
d="m 9.5000062,24.499999 0,5 1.9999998,0 1,2 3,0 1,2 2,0 0,-11 -2,0 -1,2 z"
|
||||
style="display:inline;opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path60" />
|
||||
<path
|
||||
d="m 12.500006,24.478599 0,-4.4786 c 0,0 0.172867,-10.4999998 11.5,-10.4999998 11.327157,0 11.5,10.4999998 11.5,10.4999998 l 0,4.4786"
|
||||
style="display:inline;opacity:1;fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path62" />
|
||||
<path
|
||||
style="display:inline;opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 38.500006,24.499999 0,5 -2,0 -1,2 -3,0 -1,2 -2,0 0,-11 2,0 1,2 z"
|
||||
id="path64" />
|
||||
<rect
|
||||
width="4"
|
||||
height="2"
|
||||
x="7"
|
||||
y="39"
|
||||
rx="0"
|
||||
ry="0"
|
||||
style="display:inline;opacity:0.9;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect66" />
|
||||
<rect
|
||||
style="display:inline;opacity:0.9;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="4"
|
||||
height="2"
|
||||
x="12"
|
||||
y="39"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect68" />
|
||||
<rect
|
||||
width="4"
|
||||
height="2"
|
||||
x="17"
|
||||
y="39"
|
||||
rx="0"
|
||||
ry="0"
|
||||
style="display:inline;opacity:0.9;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect70" />
|
||||
<rect
|
||||
style="display:inline;opacity:0.9;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="4"
|
||||
height="2"
|
||||
x="22"
|
||||
y="39"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect72" />
|
||||
<rect
|
||||
width="4"
|
||||
height="2"
|
||||
x="27"
|
||||
y="39"
|
||||
rx="0"
|
||||
ry="0"
|
||||
style="display:inline;opacity:0.9;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect74" />
|
||||
<rect
|
||||
style="display:inline;opacity:0.9;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="4"
|
||||
height="2"
|
||||
x="32"
|
||||
y="39"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect76" />
|
||||
<rect
|
||||
width="4"
|
||||
height="2"
|
||||
x="37"
|
||||
y="39"
|
||||
rx="0"
|
||||
ry="0"
|
||||
style="display:inline;opacity:0.9;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect78" />
|
||||
<rect
|
||||
style="display:inline;opacity:0.65;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="4"
|
||||
height="2"
|
||||
x="7"
|
||||
y="36"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect80" />
|
||||
<rect
|
||||
style="display:inline;opacity:0.65;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="4"
|
||||
height="2"
|
||||
x="17"
|
||||
y="36"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect82" />
|
||||
<rect
|
||||
width="4"
|
||||
height="2"
|
||||
x="22"
|
||||
y="36"
|
||||
rx="0"
|
||||
ry="0"
|
||||
style="display:inline;opacity:0.65;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect84" />
|
||||
<rect
|
||||
style="display:inline;opacity:0.65;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="4"
|
||||
height="2"
|
||||
x="27"
|
||||
y="36"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect86" />
|
||||
<rect
|
||||
style="display:inline;opacity:0.65;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="4"
|
||||
height="2"
|
||||
x="37"
|
||||
y="36"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect88" />
|
||||
<rect
|
||||
style="display:inline;opacity:0.4;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="4"
|
||||
height="2"
|
||||
x="22"
|
||||
y="33"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect90" />
|
||||
<rect
|
||||
width="4"
|
||||
height="2"
|
||||
x="37"
|
||||
y="33"
|
||||
rx="0"
|
||||
ry="0"
|
||||
style="display:inline;opacity:0.4;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect92" />
|
||||
<rect
|
||||
ry="1.5"
|
||||
y="3.5"
|
||||
x="3.5"
|
||||
height="41"
|
||||
width="41"
|
||||
id="rect855"
|
||||
style="fill:none;fill-opacity:1;stroke:url(#linearGradient871);stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</svg>
|
After Width: | Height: | Size: 16 KiB |
BIN
public/static/icon_64.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
373
public/static/icon_64.svg
Normal file
@ -0,0 +1,373 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="64"
|
||||
height="64"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
inkscape:version="0.92.4 5da689c313, 2019-01-14"
|
||||
sodipodi:docname="icon_64.svg"
|
||||
inkscape:export-filename="/home/djoma/Projekte/artemanufrij/app/webplay-client/public/static/icon_192.png"
|
||||
inkscape:export-xdpi="288"
|
||||
inkscape:export-ydpi="288">
|
||||
<metadata
|
||||
id="metadata96">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1611"
|
||||
inkscape:window-height="952"
|
||||
id="namedview94"
|
||||
showgrid="false"
|
||||
inkscape:zoom="8"
|
||||
inkscape:cx="31.924368"
|
||||
inkscape:cy="31.567637"
|
||||
inkscape:window-x="172"
|
||||
inkscape:window-y="92"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" />
|
||||
<defs
|
||||
id="defs4">
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient877">
|
||||
<stop
|
||||
style="stop-color:#535c65;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop873" />
|
||||
<stop
|
||||
style="stop-color:#31363b;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop875" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient861">
|
||||
<stop
|
||||
style="stop-color:#f4f4f4;stop-opacity:0.25098041"
|
||||
offset="0"
|
||||
id="stop857" />
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:0.25098041"
|
||||
offset="1"
|
||||
id="stop859" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient4256">
|
||||
<stop
|
||||
style="stop-color:#1777ba;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop7" />
|
||||
<stop
|
||||
style="stop-color:#1777ba;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop9" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3381-5-4"
|
||||
x1="24"
|
||||
x2="24"
|
||||
y1="5"
|
||||
y2="43"
|
||||
gradientTransform="matrix(1.4324324,0,0,1.4362832,-2.378381,-2.4707782)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient3924-2-2-5-8" />
|
||||
<linearGradient
|
||||
id="linearGradient3924-2-2-5-8">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop13" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0.23529412"
|
||||
offset=".063"
|
||||
id="stop15" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0.15686275"
|
||||
offset=".951"
|
||||
id="stop17" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0.39215687"
|
||||
offset="1"
|
||||
id="stop19" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
id="radialGradient3337-2-2"
|
||||
cx="4.993"
|
||||
cy="43.5"
|
||||
r="2.5"
|
||||
fx="4.993"
|
||||
fy="43.5"
|
||||
gradientTransform="matrix(3.2060543,0,0,0.99999998,37.981006,16.000001)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient3688-166-749-4-0-3-8" />
|
||||
<linearGradient
|
||||
id="linearGradient3688-166-749-4-0-3-8">
|
||||
<stop
|
||||
style="stop-color:#181818;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop23" />
|
||||
<stop
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
offset="1"
|
||||
id="stop25" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
id="radialGradient3339-1-4"
|
||||
cx="4.993"
|
||||
cy="43.5"
|
||||
r="2.5"
|
||||
fx="4.993"
|
||||
fy="43.5"
|
||||
gradientTransform="matrix(3.2060543,0,0,0.99999998,-26.018992,-103)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient3688-464-309-9-2-4-2" />
|
||||
<linearGradient
|
||||
id="linearGradient3688-464-309-9-2-4-2">
|
||||
<stop
|
||||
style="stop-color:#181818;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop29" />
|
||||
<stop
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
offset="1"
|
||||
id="stop31" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3702-501-757-8-4-1-1">
|
||||
<stop
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
offset="0"
|
||||
id="stop34" />
|
||||
<stop
|
||||
style="stop-color:#181818;stop-opacity:1"
|
||||
offset=".5"
|
||||
id="stop36" />
|
||||
<stop
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
offset="1"
|
||||
id="stop38" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient6394"
|
||||
x1="25.058"
|
||||
x2="25.058"
|
||||
y1="47.028"
|
||||
y2="39.999"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient3702-501-757-8-4-1-1"
|
||||
gradientTransform="matrix(1.5714286,0,0,0.7142857,-5.7142853,28.428572)" />
|
||||
<linearGradient
|
||||
id="linearGradient4262"
|
||||
x1="31"
|
||||
x2="31"
|
||||
y1="59"
|
||||
y2="4.737"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient4256" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient861"
|
||||
id="linearGradient863"
|
||||
x1="27"
|
||||
y1="4"
|
||||
x2="27"
|
||||
y2="60"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient877"
|
||||
id="radialGradient879"
|
||||
cx="31.625"
|
||||
cy="4"
|
||||
fx="31.625"
|
||||
fy="4"
|
||||
r="28"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0,1.9571706,-2.0851744,0,39.965698,-57.895519)" />
|
||||
</defs>
|
||||
<rect
|
||||
style="fill:url(#radialGradient879);fill-opacity:1;stroke:none;stroke-width:2.85814881;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect853"
|
||||
width="56"
|
||||
height="56"
|
||||
x="4"
|
||||
y="4"
|
||||
ry="2" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.15;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="M 32 8 C 22.542678 8 17.01237 12.828471 14.480469 17.591797 C 11.967919 22.318718 11.999934 26.988801 12 26.998047 L 12 27 L 12 27.003906 L 12 31 L 11.5 31 C 10.6835 31.000082 10.000082 31.6835 10 32.5 L 10 39.5 C 10.000082 40.3165 10.6835 40.999918 11.5 41 L 13.417969 41 L 14.076172 42.974609 C 14.27934 43.583587 14.858026 44.000335 15.5 44 L 17.417969 44 L 18.076172 45.974609 C 18.27934 46.583587 18.858026 47.000335 19.5 47 L 24.5 47 C 25.3165 46.999918 25.999918 46.3165 26 45.5 L 26 28.5 C 25.999918 27.6835 25.3165 27.000082 24.5 27 L 19.5 27 A 1.0001 1.0001 0 0 0 19.498047 27 C 18.815558 27.0011 18.210263 27.47456 18.044922 28.136719 L 18 28.316406 L 18 27.007812 L 18 27 C 17.9989 27.150353 18.04342 23.670266 19.777344 20.408203 C 21.536818 17.098064 24.422823 14 32 14 C 39.577196 14 42.463186 17.098064 44.222656 20.408203 C 45.959897 23.676524 46.001144 27.155119 46 27.007812 L 46 28.316406 L 45.955078 28.136719 C 45.789735 27.47456 45.184442 27.001063 44.501953 27 A 1.0001 1.0001 0 0 0 44.5 27 L 39.5 27 C 38.6835 27.000082 38.000082 27.6835 38 28.5 L 38 45.5 C 38.000082 46.3165 38.6835 46.999918 39.5 47 L 44.5 47 C 45.141974 47.000335 45.72066 46.583587 45.923828 45.974609 L 46.582031 44 L 48.5 44 C 49.141974 44.000335 49.72066 43.583587 49.923828 42.974609 L 50.582031 41 L 52.5 41 C 53.3165 40.999918 53.999918 40.3165 54 39.5 L 54 32.5 C 53.999918 31.6835 53.3165 31.000082 52.5 31 L 52 31 L 52 27.003906 L 52 27 C 52.000045 26.9938 52.03291 22.320279 49.519531 17.591797 C 46.987635 12.82862 41.457345 8 32 8 z"
|
||||
id="path52" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.3;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 32,8.9999999 c -9.123144,0 -14.251477,4.5731351 -16.636719,9.0605471 C 12.97804,22.547959 13,26.998047 13,26.998047 A 2.0002,2.0002 0 0 0 13,27 l 0,5 -1.5,0 A 0.50005,0.50005 0 0 0 11,32.5 l 0,7 a 0.50005,0.50005 0 0 0 0.5,0.5 l 2.638672,0 0.886719,2.658203 A 0.50005,0.50005 0 0 0 15.5,43 l 2.638672,0 0.886719,2.658203 A 0.50005,0.50005 0 0 0 19.5,46 l 5,0 A 0.50005,0.50005 0 0 0 25,45.5 l 0,-17 A 0.50005,0.50005 0 0 0 24.5,28 l -5,0 a 0.50005,0.50005 0 0 0 -0.484375,0.378906 L 18.109375,32 17,32 17,27.001953 17,27 c 2.4e-5,-0.0031 0.02826,-3.549488 1.894531,-7.060547 C 20.761615,16.426865 24.132444,13 32,13 39.867575,13 43.23839,16.426866 45.105469,19.939453 46.971734,23.45051 46.999976,26.99691 47,27 l 0,5 -1.109375,0 -0.90625,-3.621094 A 0.50005,0.50005 0 0 0 44.5,28 l -5,0 A 0.50005,0.50005 0 0 0 39,28.5 l 0,17 a 0.50005,0.50005 0 0 0 0.5,0.5 l 5,0 a 0.50005,0.50005 0 0 0 0.474609,-0.341797 L 45.861328,43 48.5,43 a 0.50005,0.50005 0 0 0 0.474609,-0.341797 L 49.861328,40 52.5,40 A 0.50005,0.50005 0 0 0 53,39.5 l 0,-7 A 0.50005,0.50005 0 0 0 52.5,32 l -1.5,0 0,-5 a 2.0002,2.0002 0 0 0 0,-0.002 c 0,0 0.02196,-4.450087 -2.363281,-8.9375 C 46.251481,13.573227 41.123161,8.9999999 32,8.9999999 Z"
|
||||
id="path54" />
|
||||
<rect
|
||||
style="opacity:0.9;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="6"
|
||||
height="4"
|
||||
x="22"
|
||||
y="52"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect60" />
|
||||
<rect
|
||||
style="opacity:0.65;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="6"
|
||||
height="4"
|
||||
x="22"
|
||||
y="47"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect62" />
|
||||
<rect
|
||||
style="opacity:0.9;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="6"
|
||||
height="4"
|
||||
x="15"
|
||||
y="52"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect64" />
|
||||
<rect
|
||||
width="6"
|
||||
height="4"
|
||||
x="8"
|
||||
y="52"
|
||||
rx="0"
|
||||
ry="0"
|
||||
style="opacity:0.9;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect66" />
|
||||
<rect
|
||||
style="opacity:0.65;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="6"
|
||||
height="4"
|
||||
x="8"
|
||||
y="47"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect68" />
|
||||
<rect
|
||||
style="opacity:0.9;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="6"
|
||||
height="4"
|
||||
x="29"
|
||||
y="52"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect70" />
|
||||
<rect
|
||||
style="opacity:0.65;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="6"
|
||||
height="4"
|
||||
x="29"
|
||||
y="47"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect72" />
|
||||
<rect
|
||||
width="6"
|
||||
height="4"
|
||||
x="29"
|
||||
y="42"
|
||||
rx="0"
|
||||
ry="0"
|
||||
style="opacity:0.4;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect74" />
|
||||
<rect
|
||||
style="opacity:0.9;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="6"
|
||||
height="4"
|
||||
x="36"
|
||||
y="52"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect76" />
|
||||
<rect
|
||||
style="opacity:0.9;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="6"
|
||||
height="4"
|
||||
x="43"
|
||||
y="52"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect78" />
|
||||
<rect
|
||||
width="6"
|
||||
height="4"
|
||||
x="50"
|
||||
y="52"
|
||||
rx="0"
|
||||
ry="0"
|
||||
style="opacity:0.9;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect80" />
|
||||
<rect
|
||||
width="6"
|
||||
height="4"
|
||||
x="50"
|
||||
y="47"
|
||||
rx="0"
|
||||
ry="0"
|
||||
style="opacity:0.65;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect82" />
|
||||
<rect
|
||||
style="opacity:0.4;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="6"
|
||||
height="4"
|
||||
x="50"
|
||||
y="42"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect84" />
|
||||
<rect
|
||||
width="6"
|
||||
height="4"
|
||||
x="36"
|
||||
y="47"
|
||||
rx="0"
|
||||
ry="0"
|
||||
style="opacity:0.65;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect86" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
||||
d="m 11.499998,31.49999 0,7 3,0 1,3 3,0 1,3 5,0 0,-17 -5,0 -1,4 z"
|
||||
id="path88" />
|
||||
<path
|
||||
d="m 52.499998,31.49999 0,7 -3,0 -1,3 -3,0 -1,3 -5,0 0,-17 5,0 1,4 z"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
||||
id="path90" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 14.999998,30.99999 0,-5 c 0,0 0.0093,-16.0000004 17,-16.0000004 16.990736,0 17,16.0000004 17,16.0000004 l 0,5"
|
||||
id="path92" />
|
||||
<rect
|
||||
ry="1.5"
|
||||
y="4.5"
|
||||
x="4.5"
|
||||
height="55"
|
||||
width="55"
|
||||
id="rect855"
|
||||
style="fill:none;fill-opacity:1;stroke:url(#linearGradient863);stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</svg>
|
After Width: | Height: | Size: 16 KiB |
322
public/static/icon_64_flat.svg
Normal file
@ -0,0 +1,322 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="64"
|
||||
height="64"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20, custom)"
|
||||
sodipodi:docname="icon_64_flat.svg"
|
||||
inkscape:export-filename="/home/djoma/Projekte/artemanufrij/app/webplay-client/public/static/icon_192.png"
|
||||
inkscape:export-xdpi="288"
|
||||
inkscape:export-ydpi="288"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<metadata
|
||||
id="metadata96">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1611"
|
||||
inkscape:window-height="952"
|
||||
id="namedview94"
|
||||
showgrid="false"
|
||||
inkscape:zoom="8"
|
||||
inkscape:cx="38.75"
|
||||
inkscape:cy="31.625"
|
||||
inkscape:window-x="172"
|
||||
inkscape:window-y="92"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2"
|
||||
inkscape:pagecheckerboard="0" />
|
||||
<defs
|
||||
id="defs4">
|
||||
<linearGradient
|
||||
id="linearGradient4256">
|
||||
<stop
|
||||
style="stop-color:#1777ba;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop7" />
|
||||
<stop
|
||||
style="stop-color:#1777ba;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop9" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3381-5-4"
|
||||
x1="24"
|
||||
x2="24"
|
||||
y1="5"
|
||||
y2="43"
|
||||
gradientTransform="matrix(1.4324324,0,0,1.4362832,-2.378381,-2.4707782)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient3924-2-2-5-8" />
|
||||
<linearGradient
|
||||
id="linearGradient3924-2-2-5-8">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop13" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0.23529412"
|
||||
offset=".063"
|
||||
id="stop15" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0.15686275"
|
||||
offset=".951"
|
||||
id="stop17" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0.39215687"
|
||||
offset="1"
|
||||
id="stop19" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
id="radialGradient3337-2-2"
|
||||
cx="4.993"
|
||||
cy="43.5"
|
||||
r="2.5"
|
||||
fx="4.993"
|
||||
fy="43.5"
|
||||
gradientTransform="matrix(3.2060543,0,0,0.99999998,37.981006,16.000001)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient3688-166-749-4-0-3-8" />
|
||||
<linearGradient
|
||||
id="linearGradient3688-166-749-4-0-3-8">
|
||||
<stop
|
||||
style="stop-color:#181818;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop23" />
|
||||
<stop
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
offset="1"
|
||||
id="stop25" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
id="radialGradient3339-1-4"
|
||||
cx="4.993"
|
||||
cy="43.5"
|
||||
r="2.5"
|
||||
fx="4.993"
|
||||
fy="43.5"
|
||||
gradientTransform="matrix(3.2060543,0,0,0.99999998,-26.018992,-103)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient3688-464-309-9-2-4-2" />
|
||||
<linearGradient
|
||||
id="linearGradient3688-464-309-9-2-4-2">
|
||||
<stop
|
||||
style="stop-color:#181818;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop29" />
|
||||
<stop
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
offset="1"
|
||||
id="stop31" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3702-501-757-8-4-1-1">
|
||||
<stop
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
offset="0"
|
||||
id="stop34" />
|
||||
<stop
|
||||
style="stop-color:#181818;stop-opacity:1"
|
||||
offset=".5"
|
||||
id="stop36" />
|
||||
<stop
|
||||
style="stop-color:#181818;stop-opacity:0"
|
||||
offset="1"
|
||||
id="stop38" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient6394"
|
||||
x1="25.058"
|
||||
x2="25.058"
|
||||
y1="47.028"
|
||||
y2="39.999"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient3702-501-757-8-4-1-1"
|
||||
gradientTransform="matrix(1.5714286,0,0,0.7142857,-5.7142853,28.428572)" />
|
||||
<linearGradient
|
||||
id="linearGradient4262"
|
||||
x1="31"
|
||||
x2="31"
|
||||
y1="59"
|
||||
y2="4.737"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
xlink:href="#linearGradient4256" />
|
||||
</defs>
|
||||
<rect
|
||||
style="fill:#31363b;fill-opacity:0;stroke:none;stroke-width:2.85814881;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect853"
|
||||
width="56"
|
||||
height="56"
|
||||
x="4"
|
||||
y="4"
|
||||
ry="2" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.15;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="M 32 8 C 22.542678 8 17.01237 12.828471 14.480469 17.591797 C 11.967919 22.318718 11.999934 26.988801 12 26.998047 L 12 27 L 12 27.003906 L 12 31 L 11.5 31 C 10.6835 31.000082 10.000082 31.6835 10 32.5 L 10 39.5 C 10.000082 40.3165 10.6835 40.999918 11.5 41 L 13.417969 41 L 14.076172 42.974609 C 14.27934 43.583587 14.858026 44.000335 15.5 44 L 17.417969 44 L 18.076172 45.974609 C 18.27934 46.583587 18.858026 47.000335 19.5 47 L 24.5 47 C 25.3165 46.999918 25.999918 46.3165 26 45.5 L 26 28.5 C 25.999918 27.6835 25.3165 27.000082 24.5 27 L 19.5 27 A 1.0001 1.0001 0 0 0 19.498047 27 C 18.815558 27.0011 18.210263 27.47456 18.044922 28.136719 L 18 28.316406 L 18 27.007812 L 18 27 C 17.9989 27.150353 18.04342 23.670266 19.777344 20.408203 C 21.536818 17.098064 24.422823 14 32 14 C 39.577196 14 42.463186 17.098064 44.222656 20.408203 C 45.959897 23.676524 46.001144 27.155119 46 27.007812 L 46 28.316406 L 45.955078 28.136719 C 45.789735 27.47456 45.184442 27.001063 44.501953 27 A 1.0001 1.0001 0 0 0 44.5 27 L 39.5 27 C 38.6835 27.000082 38.000082 27.6835 38 28.5 L 38 45.5 C 38.000082 46.3165 38.6835 46.999918 39.5 47 L 44.5 47 C 45.141974 47.000335 45.72066 46.583587 45.923828 45.974609 L 46.582031 44 L 48.5 44 C 49.141974 44.000335 49.72066 43.583587 49.923828 42.974609 L 50.582031 41 L 52.5 41 C 53.3165 40.999918 53.999918 40.3165 54 39.5 L 54 32.5 C 53.999918 31.6835 53.3165 31.000082 52.5 31 L 52 31 L 52 27.003906 L 52 27 C 52.000045 26.9938 52.03291 22.320279 49.519531 17.591797 C 46.987635 12.82862 41.457345 8 32 8 z"
|
||||
id="path52" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.3;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 32,8.9999999 c -9.123144,0 -14.251477,4.5731351 -16.636719,9.0605471 C 12.97804,22.547959 13,26.998047 13,26.998047 A 2.0002,2.0002 0 0 0 13,27 l 0,5 -1.5,0 A 0.50005,0.50005 0 0 0 11,32.5 l 0,7 a 0.50005,0.50005 0 0 0 0.5,0.5 l 2.638672,0 0.886719,2.658203 A 0.50005,0.50005 0 0 0 15.5,43 l 2.638672,0 0.886719,2.658203 A 0.50005,0.50005 0 0 0 19.5,46 l 5,0 A 0.50005,0.50005 0 0 0 25,45.5 l 0,-17 A 0.50005,0.50005 0 0 0 24.5,28 l -5,0 a 0.50005,0.50005 0 0 0 -0.484375,0.378906 L 18.109375,32 17,32 17,27.001953 17,27 c 2.4e-5,-0.0031 0.02826,-3.549488 1.894531,-7.060547 C 20.761615,16.426865 24.132444,13 32,13 39.867575,13 43.23839,16.426866 45.105469,19.939453 46.971734,23.45051 46.999976,26.99691 47,27 l 0,5 -1.109375,0 -0.90625,-3.621094 A 0.50005,0.50005 0 0 0 44.5,28 l -5,0 A 0.50005,0.50005 0 0 0 39,28.5 l 0,17 a 0.50005,0.50005 0 0 0 0.5,0.5 l 5,0 a 0.50005,0.50005 0 0 0 0.474609,-0.341797 L 45.861328,43 48.5,43 a 0.50005,0.50005 0 0 0 0.474609,-0.341797 L 49.861328,40 52.5,40 A 0.50005,0.50005 0 0 0 53,39.5 l 0,-7 A 0.50005,0.50005 0 0 0 52.5,32 l -1.5,0 0,-5 a 2.0002,2.0002 0 0 0 0,-0.002 c 0,0 0.02196,-4.450087 -2.363281,-8.9375 C 46.251481,13.573227 41.123161,8.9999999 32,8.9999999 Z"
|
||||
id="path54" />
|
||||
<rect
|
||||
style="opacity:0.9;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="6"
|
||||
height="4"
|
||||
x="22"
|
||||
y="52"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect60" />
|
||||
<rect
|
||||
style="opacity:0.65;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="6"
|
||||
height="4"
|
||||
x="22"
|
||||
y="47"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect62" />
|
||||
<rect
|
||||
style="opacity:0.9;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="6"
|
||||
height="4"
|
||||
x="15"
|
||||
y="52"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect64" />
|
||||
<rect
|
||||
width="6"
|
||||
height="4"
|
||||
x="8"
|
||||
y="52"
|
||||
rx="0"
|
||||
ry="0"
|
||||
style="opacity:0.9;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect66" />
|
||||
<rect
|
||||
style="opacity:0.65;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="6"
|
||||
height="4"
|
||||
x="8"
|
||||
y="47"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect68" />
|
||||
<rect
|
||||
style="opacity:0.9;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="6"
|
||||
height="4"
|
||||
x="29"
|
||||
y="52"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect70" />
|
||||
<rect
|
||||
style="opacity:0.65;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="6"
|
||||
height="4"
|
||||
x="29"
|
||||
y="47"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect72" />
|
||||
<rect
|
||||
width="6"
|
||||
height="4"
|
||||
x="29"
|
||||
y="42"
|
||||
rx="0"
|
||||
ry="0"
|
||||
style="opacity:0.4;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect74" />
|
||||
<rect
|
||||
style="opacity:0.9;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="6"
|
||||
height="4"
|
||||
x="36"
|
||||
y="52"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect76" />
|
||||
<rect
|
||||
style="opacity:0.9;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="6"
|
||||
height="4"
|
||||
x="43"
|
||||
y="52"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect78" />
|
||||
<rect
|
||||
width="6"
|
||||
height="4"
|
||||
x="50"
|
||||
y="52"
|
||||
rx="0"
|
||||
ry="0"
|
||||
style="opacity:0.9;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect80" />
|
||||
<rect
|
||||
width="6"
|
||||
height="4"
|
||||
x="50"
|
||||
y="47"
|
||||
rx="0"
|
||||
ry="0"
|
||||
style="opacity:0.65;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect82" />
|
||||
<rect
|
||||
style="opacity:0.4;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
width="6"
|
||||
height="4"
|
||||
x="50"
|
||||
y="42"
|
||||
rx="0"
|
||||
ry="0"
|
||||
id="rect84" />
|
||||
<rect
|
||||
width="6"
|
||||
height="4"
|
||||
x="36"
|
||||
y="47"
|
||||
rx="0"
|
||||
ry="0"
|
||||
style="opacity:0.65;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect86" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
||||
d="m 11.499998,31.49999 0,7 3,0 1,3 3,0 1,3 5,0 0,-17 -5,0 -1,4 z"
|
||||
id="path88" />
|
||||
<path
|
||||
d="m 52.499998,31.49999 0,7 -3,0 -1,3 -3,0 -1,3 -5,0 0,-17 5,0 1,4 z"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
||||
id="path90" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 14.999998,30.99999 0,-5 c 0,0 0.0093,-16.0000004 17,-16.0000004 16.990736,0 17,16.0000004 17,16.0000004 l 0,5"
|
||||
id="path92" />
|
||||
</svg>
|
After Width: | Height: | Size: 15 KiB |
BIN
public/static/icon_96.png
Normal file
After Width: | Height: | Size: 5.7 KiB |
73
public/static/icons/dummy/album.svg
Normal file
@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
id="svg8"
|
||||
version="1.1"
|
||||
width="16"
|
||||
height="16"
|
||||
sodipodi:docname="album_gray.svg"
|
||||
inkscape:version="0.92.4 5da689c313, 2019-01-14">
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="931"
|
||||
inkscape:window-height="480"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
inkscape:zoom="14.75"
|
||||
inkscape:cx="-9.3220339"
|
||||
inkscape:cy="8"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg8" />
|
||||
<metadata
|
||||
id="metadata14">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs12" />
|
||||
<g
|
||||
style="fill:#7f8c8d;fill-opacity:1"
|
||||
id="g6"
|
||||
transform="translate(-1233 -61)"
|
||||
color="#bebebe">
|
||||
<path
|
||||
id="path2"
|
||||
white-space="normal"
|
||||
style="line-height:normal;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000;text-transform:none;text-orientation:mixed;shape-padding:0;isolation:auto;mix-blend-mode:normal;marker:none;fill:#7f8c8d;fill-opacity:1"
|
||||
overflow="visible"
|
||||
font-weight="400"
|
||||
font-family="sans-serif"
|
||||
fill="#666"
|
||||
d="M1241 62c-3.842 0-7 3.158-7 7s3.158 7 7 7 7-3.158 7-7-3.158-7-7-7zm0 4c1.68 0 3 1.32 3 3s-1.32 3-3 3-3-1.32-3-3 1.32-3 3-3z" />
|
||||
<path
|
||||
id="path4"
|
||||
style="line-height:normal;-inkscape-font-specification:Sans;text-indent:0;text-align:start;text-decoration-line:none;text-transform:none;marker:none;fill:#7f8c8d;fill-opacity:1"
|
||||
overflow="visible"
|
||||
font-weight="400"
|
||||
font-family="Sans"
|
||||
fill="#666"
|
||||
d="M1241.005 67.006c-1.098 0-2.013.898-2.013 1.996 0 1.099.915 1.997 2.013 1.997a2.002 2.002 0 0 0 1.997-1.997 2.002 2.002 0 0 0-1.997-1.996z" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.7 KiB |
67
public/static/icons/dummy/artist.svg
Normal file
@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
height="16"
|
||||
width="32"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
sodipodi:docname="artist-placeholder.svg"
|
||||
inkscape:version="0.92.4 5da689c313, 2019-01-14">
|
||||
<metadata
|
||||
id="metadata14">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs12" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="2130"
|
||||
inkscape:window-height="939"
|
||||
id="namedview10"
|
||||
showgrid="false"
|
||||
inkscape:zoom="14.75"
|
||||
inkscape:cx="0.54237288"
|
||||
inkscape:cy="8"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg8" />
|
||||
<g
|
||||
transform="translate(-585,171)"
|
||||
id="g6"
|
||||
style="fill:#7f8c8d;fill-opacity:1">
|
||||
<path
|
||||
d="m 601,-171 a 8,8 0 1 0 0,16 8,8 0 0 0 0,-16 z m 0,1 a 7,7 0 0 1 7,7 c 0,1.972 -0.844,3.728 -2.156,5 -0.443,-1.727 -1.975,-3 -3.844,-3 h -2 c -1.869,0 -3.4,1.273 -3.844,3 -1.312,-1.272 -2.156,-3.028 -2.156,-5 a 7,7 0 0 1 7,-7 z"
|
||||
overflow="visible"
|
||||
style="color:#000000;overflow:visible;fill:#7f8c8d;fill-opacity:1;marker:none"
|
||||
id="path2"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="m 601,-167.75 c -1.795,0 -3.25,1.4 -3.25,3.125 0,1.726 1.455,3.125 3.25,3.125 1.795,0 3.25,-1.4 3.25,-3.125 0,-1.726 -1.455,-3.125 -3.25,-3.125 z"
|
||||
overflow="visible"
|
||||
style="color:#000000;overflow:visible;fill:#7f8c8d;fill-opacity:1;marker:none"
|
||||
id="path4"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
61
public/static/icons/dummy/box.svg
Normal file
@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
height="23"
|
||||
width="16"
|
||||
version="1.1"
|
||||
id="svg6"
|
||||
sodipodi:docname="box-placeholder.svg"
|
||||
inkscape:version="0.92.4 5da689c313, 2019-01-14">
|
||||
<metadata
|
||||
id="metadata12">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs10" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="882"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
inkscape:zoom="14.75"
|
||||
inkscape:cx="-11.050847"
|
||||
inkscape:cy="8"
|
||||
inkscape:window-x="766"
|
||||
inkscape:window-y="164"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg6" />
|
||||
<g
|
||||
transform="translate(-1113,270)"
|
||||
id="g4"
|
||||
style="color:#bebebe;fill:#7f8c8d;fill-opacity:1">
|
||||
<path
|
||||
d="m 1114.75,-266 a 0.748,0.748 0 0 0 -0.75,0.75 v 13.5 c 0,0.415 0.335,0.75 0.75,0.75 h 12.5 c 0.416,0 0.75,-0.335 0.75,-0.75 v -13.5 a 0.748,0.748 0 0 0 -0.75,-0.75 z m 0.25,1 h 1 v 1 h -1 z m 2.5,0 h 7 c 0.277,0 0.5,0.223 0.5,0.5 v 5 c 0,0.277 -0.223,0.5 -0.5,0.5 h -7 a 0.499,0.499 0 0 1 -0.5,-0.5 v -5 c 0,-0.277 0.223,-0.5 0.5,-0.5 z m 8.5,0 h 1 v 1 h -1 z m -11,2 h 1 v 1 h -1 z m 11,0 h 1 v 1 h -1 z m -11,2 h 1 v 1 h -1 z m 11,0 h 1 v 1 h -1 z m -11,2 h 1 v 1 h -1 z m 11,0 h 1 v 1 h -1 z m -8.5,1 h 7 c 0.277,0 0.5,0.223 0.5,0.5 v 5 c 0,0.277 -0.223,0.5 -0.5,0.5 h -7 a 0.499,0.499 0 0 1 -0.5,-0.5 v -5 c 0,-0.277 0.223,-0.5 0.5,-0.5 z m -2.5,1 h 1 v 1 h -1 z m 11,0 h 1 v 1 h -1 z m -11,2 h 1 v 1 h -1 z m 11,0 h 1 v 1 h -1 z m -11,2 h 1 v 1 h -1 z m 11,0 h 1 v 1 h -1 z"
|
||||
overflow="visible"
|
||||
style="overflow:visible;fill:#7f8c8d;fill-opacity:1;marker:none"
|
||||
id="path2"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.5 KiB |
62
public/static/icons/media-consecutive-dark.svg
Normal file
@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="16"
|
||||
width="16"
|
||||
version="1.1"
|
||||
id="svg6"
|
||||
sodipodi:docname="media-consecutive-dark.svg"
|
||||
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20, custom)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<metadata
|
||||
id="metadata12">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs10" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1578"
|
||||
inkscape:window-height="779"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
inkscape:zoom="14.75"
|
||||
inkscape:cx="5.1186441"
|
||||
inkscape:cy="8"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="36"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg6"
|
||||
inkscape:pagecheckerboard="0" />
|
||||
<g
|
||||
color="#bebebe"
|
||||
transform="translate(-333 151)"
|
||||
id="g4"
|
||||
style="fill:#000000;fill-opacity:1">
|
||||
<path
|
||||
d="M344-143v2h-10v2h10v2l4-3zm0-6v2h-10v2h10v2l4-3z"
|
||||
fill="#666"
|
||||
overflow="visible"
|
||||
style="marker:none;fill:#000000;fill-opacity:1"
|
||||
id="path2" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
61
public/static/icons/media-consecutive.svg
Normal file
@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
height="16"
|
||||
width="16"
|
||||
version="1.1"
|
||||
id="svg6"
|
||||
sodipodi:docname="media-consecutive.svg"
|
||||
inkscape:version="0.92.4 5da689c313, 2019-01-14">
|
||||
<metadata
|
||||
id="metadata12">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs10" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="931"
|
||||
inkscape:window-height="480"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
inkscape:zoom="14.75"
|
||||
inkscape:cx="-5.7288136"
|
||||
inkscape:cy="8"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="26"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg6" />
|
||||
<g
|
||||
color="#bebebe"
|
||||
transform="translate(-333 151)"
|
||||
id="g4"
|
||||
style="fill:#ffffff;fill-opacity:1">
|
||||
<path
|
||||
d="M344-143v2h-10v2h10v2l4-3zm0-6v2h-10v2h10v2l4-3z"
|
||||
fill="#666"
|
||||
overflow="visible"
|
||||
style="marker:none;fill:#ffffff;fill-opacity:1"
|
||||
id="path2" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
63
public/static/icons/media-no-repeat-dark.svg
Normal file
@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="16"
|
||||
width="16"
|
||||
version="1.1"
|
||||
id="svg6"
|
||||
sodipodi:docname="media-no-repeat-dark.svg"
|
||||
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20, custom)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<metadata
|
||||
id="metadata12">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs10" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1244"
|
||||
inkscape:window-height="710"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
inkscape:zoom="14.75"
|
||||
inkscape:cx="-5.7288136"
|
||||
inkscape:cy="7.9661017"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="36"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg6"
|
||||
inkscape:pagecheckerboard="0" />
|
||||
<g
|
||||
color="#bebebe"
|
||||
transform="translate(-293 151)"
|
||||
id="g4"
|
||||
style="fill:#000000;fill-opacity:0.50196081">
|
||||
<path
|
||||
d="M299-150v2h-1c-2.77 0-5 2.23-5 5s2.23 5 5 5v-2c-1.662 0-3-1.338-3-3s1.338-3 3-3h1v2l4-3zm5 2v2c1.662 0 3 1.338 3 3s-1.338 3-3 3h-1v-2l-4 3 4 3v-2h1c2.77 0 5-2.23 5-5s-2.23-5-5-5z"
|
||||
fill="#666"
|
||||
opacity=".35"
|
||||
overflow="visible"
|
||||
style="marker:none;fill:#000000;fill-opacity:0.50196081"
|
||||
id="path2" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
62
public/static/icons/media-no-repeat.svg
Normal file
@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
height="16"
|
||||
width="16"
|
||||
version="1.1"
|
||||
id="svg6"
|
||||
sodipodi:docname="media-no-repeat.svg"
|
||||
inkscape:version="0.92.4 5da689c313, 2019-01-14">
|
||||
<metadata
|
||||
id="metadata12">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs10" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="931"
|
||||
inkscape:window-height="480"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
inkscape:zoom="14.75"
|
||||
inkscape:cx="-5.7288136"
|
||||
inkscape:cy="8"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="26"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg6" />
|
||||
<g
|
||||
color="#bebebe"
|
||||
transform="translate(-293 151)"
|
||||
id="g4"
|
||||
style="fill:#ffffff;fill-opacity:0.50196081">
|
||||
<path
|
||||
d="M299-150v2h-1c-2.77 0-5 2.23-5 5s2.23 5 5 5v-2c-1.662 0-3-1.338-3-3s1.338-3 3-3h1v2l4-3zm5 2v2c1.662 0 3 1.338 3 3s-1.338 3-3 3h-1v-2l-4 3 4 3v-2h1c2.77 0 5-2.23 5-5s-2.23-5-5-5z"
|
||||
fill="#666"
|
||||
opacity=".35"
|
||||
overflow="visible"
|
||||
style="marker:none;fill:#ffffff;fill-opacity:0.50196081"
|
||||
id="path2" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
62
public/static/icons/media-repeat-dark.svg
Normal file
@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="16"
|
||||
width="16"
|
||||
version="1.1"
|
||||
id="svg6"
|
||||
sodipodi:docname="media-repeat-dark.svg"
|
||||
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20, custom)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<metadata
|
||||
id="metadata12">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs10" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1638"
|
||||
inkscape:window-height="826"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
inkscape:zoom="14.75"
|
||||
inkscape:cx="-5.7966102"
|
||||
inkscape:cy="7.9661017"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="36"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg6"
|
||||
inkscape:pagecheckerboard="0" />
|
||||
<g
|
||||
color="#bebebe"
|
||||
transform="translate(-273 151)"
|
||||
id="g4"
|
||||
style="fill:#000000;fill-opacity:1">
|
||||
<path
|
||||
d="M279-150v2h-1c-2.77 0-5 2.23-5 5s2.23 5 5 5v-2c-1.662 0-3-1.338-3-3s1.338-3 3-3h1v2l4-3zm5 2v2c1.662 0 3 1.338 3 3s-1.338 3-3 3h-1v-2l-4 3 4 3v-2h1c2.77 0 5-2.23 5-5s-2.23-5-5-5z"
|
||||
fill="#666"
|
||||
overflow="visible"
|
||||
style="marker:none;fill:#000000;fill-opacity:1"
|
||||
id="path2" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
62
public/static/icons/media-repeat-song-dark.svg
Normal file
@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="16"
|
||||
width="16"
|
||||
version="1.1"
|
||||
id="svg6"
|
||||
sodipodi:docname="media-repeat-song-dark.svg"
|
||||
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20, custom)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<metadata
|
||||
id="metadata12">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs10" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1705"
|
||||
inkscape:window-height="767"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
inkscape:zoom="14.75"
|
||||
inkscape:cx="-5.6949153"
|
||||
inkscape:cy="8"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="36"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg6"
|
||||
inkscape:pagecheckerboard="0" />
|
||||
<g
|
||||
color="#bebebe"
|
||||
transform="translate(-313 151)"
|
||||
id="g4"
|
||||
style="fill:#000000;fill-opacity:1">
|
||||
<path
|
||||
d="M319-150v2h-1c-2.77 0-5 2.23-5 5s2.23 5 5 5v-2c-1.662 0-3-1.338-3-3s1.338-3 3-3h1v2l4-3zm5 2v2a2.98 2.98 0 0 1 2.531 1.406 5.483 5.483 0 0 1 2.438 1.906c.007-.106.031-.204.031-.312 0-2.77-2.23-5-5-5zm.5 4a4.5 4.5 0 1 0 0 9 4.5 4.5 0 0 0 0-9zm-.5 2h1v4h1v1h-3v-1h1v-2h-1v-1c1 0 1-1 1-1zm-4.969 2.969l-.03.031.03.031c-.002-.02.002-.042 0-.062z"
|
||||
fill="#666"
|
||||
overflow="visible"
|
||||
style="marker:none;fill:#000000;fill-opacity:1"
|
||||
id="path2" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
61
public/static/icons/media-repeat-song.svg
Normal file
@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
height="16"
|
||||
width="16"
|
||||
version="1.1"
|
||||
id="svg6"
|
||||
sodipodi:docname="media-repeat-song.svg"
|
||||
inkscape:version="0.92.4 5da689c313, 2019-01-14">
|
||||
<metadata
|
||||
id="metadata12">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs10" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="931"
|
||||
inkscape:window-height="480"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
inkscape:zoom="14.75"
|
||||
inkscape:cx="-5.7288136"
|
||||
inkscape:cy="8"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="26"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg6" />
|
||||
<g
|
||||
color="#bebebe"
|
||||
transform="translate(-313 151)"
|
||||
id="g4"
|
||||
style="fill:#ffffff;fill-opacity:1">
|
||||
<path
|
||||
d="M319-150v2h-1c-2.77 0-5 2.23-5 5s2.23 5 5 5v-2c-1.662 0-3-1.338-3-3s1.338-3 3-3h1v2l4-3zm5 2v2a2.98 2.98 0 0 1 2.531 1.406 5.483 5.483 0 0 1 2.438 1.906c.007-.106.031-.204.031-.312 0-2.77-2.23-5-5-5zm.5 4a4.5 4.5 0 1 0 0 9 4.5 4.5 0 0 0 0-9zm-.5 2h1v4h1v1h-3v-1h1v-2h-1v-1c1 0 1-1 1-1zm-4.969 2.969l-.03.031.03.031c-.002-.02.002-.042 0-.062z"
|
||||
fill="#666"
|
||||
overflow="visible"
|
||||
style="marker:none;fill:#ffffff;fill-opacity:1"
|
||||
id="path2" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
61
public/static/icons/media-repeat.svg
Normal file
@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
height="16"
|
||||
width="16"
|
||||
version="1.1"
|
||||
id="svg6"
|
||||
sodipodi:docname="media-repeat.svg"
|
||||
inkscape:version="0.92.4 5da689c313, 2019-01-14">
|
||||
<metadata
|
||||
id="metadata12">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs10" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="931"
|
||||
inkscape:window-height="480"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
inkscape:zoom="14.75"
|
||||
inkscape:cx="-5.7966102"
|
||||
inkscape:cy="8"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="26"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg6" />
|
||||
<g
|
||||
color="#bebebe"
|
||||
transform="translate(-273 151)"
|
||||
id="g4"
|
||||
style="fill:#ffffff;fill-opacity:1">
|
||||
<path
|
||||
d="M279-150v2h-1c-2.77 0-5 2.23-5 5s2.23 5 5 5v-2c-1.662 0-3-1.338-3-3s1.338-3 3-3h1v2l4-3zm5 2v2c1.662 0 3 1.338 3 3s-1.338 3-3 3h-1v-2l-4 3 4 3v-2h1c2.77 0 5-2.23 5-5s-2.23-5-5-5z"
|
||||
fill="#666"
|
||||
overflow="visible"
|
||||
style="marker:none;fill:#ffffff;fill-opacity:1"
|
||||
id="path2" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
62
public/static/icons/media-shuffle-dark.svg
Normal file
@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="16"
|
||||
width="16"
|
||||
version="1.1"
|
||||
id="svg6"
|
||||
sodipodi:docname="media-shuffle-dark.svg"
|
||||
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20, custom)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<metadata
|
||||
id="metadata12">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs10" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1116"
|
||||
inkscape:window-height="688"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
inkscape:zoom="14.75"
|
||||
inkscape:cx="-5.7966102"
|
||||
inkscape:cy="7.9661017"
|
||||
inkscape:window-x="643"
|
||||
inkscape:window-y="222"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg6"
|
||||
inkscape:pagecheckerboard="0" />
|
||||
<g
|
||||
color="#bebebe"
|
||||
transform="translate(-353 151)"
|
||||
id="g4"
|
||||
style="fill:#000000;fill-opacity:1">
|
||||
<path
|
||||
d="M364-149v2c-2.98 0-6.024 6-8 6h-2v2h2c2.994 0 5.996-6 8-6v2l4-3zm-10 2v2h2c.582 0 1.262.548 2 1.281.12-.131.252-.25.375-.375.307-.325.628-.663.938-1-1.072-1.054-2.19-1.906-3.313-1.906zm10 4v2c-.591 0-1.292-.545-2.031-1.281-.424.455-.857.916-1.281 1.375 1.075 1.054 2.194 1.906 3.312 1.906v2l4-3z"
|
||||
fill="#666"
|
||||
overflow="visible"
|
||||
style="marker:none;fill:#000000;fill-opacity:1"
|
||||
id="path2" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
61
public/static/icons/media-shuffle.svg
Normal file
@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
height="16"
|
||||
width="16"
|
||||
version="1.1"
|
||||
id="svg6"
|
||||
sodipodi:docname="media-shuffle.svg"
|
||||
inkscape:version="0.92.4 5da689c313, 2019-01-14">
|
||||
<metadata
|
||||
id="metadata12">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs10" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="931"
|
||||
inkscape:window-height="480"
|
||||
id="namedview8"
|
||||
showgrid="false"
|
||||
inkscape:zoom="14.75"
|
||||
inkscape:cx="-5.7966102"
|
||||
inkscape:cy="8"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="26"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg6" />
|
||||
<g
|
||||
color="#bebebe"
|
||||
transform="translate(-353 151)"
|
||||
id="g4"
|
||||
style="fill:#ffffff;fill-opacity:1">
|
||||
<path
|
||||
d="M364-149v2c-2.98 0-6.024 6-8 6h-2v2h2c2.994 0 5.996-6 8-6v2l4-3zm-10 2v2h2c.582 0 1.262.548 2 1.281.12-.131.252-.25.375-.375.307-.325.628-.663.938-1-1.072-1.054-2.19-1.906-3.313-1.906zm10 4v2c-.591 0-1.292-.545-2.031-1.281-.424.455-.857.916-1.281 1.375 1.075 1.054 2.194 1.906 3.312 1.906v2l4-3z"
|
||||
fill="#666"
|
||||
overflow="visible"
|
||||
style="marker:none;fill:#ffffff;fill-opacity:1"
|
||||
id="path2" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
1544
public/static/style.css
Normal file
BIN
screenshots/albums.png
Normal file
After Width: | Height: | Size: 445 KiB |
BIN
screenshots/artists.png
Normal file
After Width: | Height: | Size: 463 KiB |
BIN
screenshots/home.png
Normal file
After Width: | Height: | Size: 517 KiB |
BIN
screenshots/mobile_album.png
Normal file
After Width: | Height: | Size: 122 KiB |
BIN
screenshots/mobile_artist.png
Normal file
After Width: | Height: | Size: 85 KiB |
BIN
screenshots/mobile_boxes.png
Normal file
After Width: | Height: | Size: 200 KiB |
BIN
screenshots/mobile_home.png
Normal file
After Width: | Height: | Size: 95 KiB |
BIN
screenshots/upload_audio.png
Normal file
After Width: | Height: | Size: 183 KiB |
BIN
screenshots/videos.png
Normal file
After Width: | Height: | Size: 777 KiB |
289
src/App.vue
Normal file
@ -0,0 +1,289 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<nav
|
||||
v-show="$route.path != '/login' && $route.path != '/setup'"
|
||||
:class="{ slideOverTop: $store.getters.isDialogOpen }"
|
||||
>
|
||||
<div>
|
||||
<router-link to="/" title="Home" class="primary">
|
||||
<awesome-icon icon="home" />
|
||||
</router-link>
|
||||
<router-link to="/albums" title="Albums">
|
||||
<awesome-icon icon="compact-disc" />
|
||||
<span class="navigation-title">Albums</span>
|
||||
</router-link>
|
||||
<router-link to="/artists" title="Artists">
|
||||
<awesome-icon icon="user-circle" />
|
||||
<span class="navigation-title">Artists</span>
|
||||
</router-link>
|
||||
<router-link to="/radios" title="Online Radio">
|
||||
<awesome-icon icon="podcast" />
|
||||
<span class="navigation-title">Radios</span>
|
||||
</router-link>
|
||||
<router-link to="/boxes" title="Boxes">
|
||||
<awesome-icon icon="film" />
|
||||
<span class="navigation-title">Videos</span>
|
||||
</router-link>
|
||||
<router-link
|
||||
to="/favourites"
|
||||
title="Favourites"
|
||||
v-if="user.favourites.length > 0"
|
||||
>
|
||||
<awesome-icon icon="star" />
|
||||
<span class="navigation-title">Favourites</span>
|
||||
</router-link>
|
||||
</div>
|
||||
<div></div>
|
||||
<div>
|
||||
<input
|
||||
type="search"
|
||||
@focus="gotoSearchView"
|
||||
@search="searchTermChanged"
|
||||
incremental
|
||||
v-model.lazy="$store.state.search.term"
|
||||
placeholder="Search…"
|
||||
autocomplete="one-time-code"
|
||||
class="hideOnMobile"
|
||||
/>
|
||||
<DropDown v-if="user.token">
|
||||
<template v-slot:default>
|
||||
<button>
|
||||
<awesome-icon icon="cog" />
|
||||
</button>
|
||||
</template>
|
||||
<template v-slot:dropdown-content>
|
||||
<button
|
||||
v-for="(menuItem, i) in $store.getters.viewMenu"
|
||||
:key="i"
|
||||
@click="menuItem.event"
|
||||
>
|
||||
<awesome-icon :icon="menuItem.icon" />{{ menuItem.title }}
|
||||
</button>
|
||||
<hr v-if="$store.getters.viewMenu.length > 0" />
|
||||
<button
|
||||
@click="openDialog('dialogUsers')"
|
||||
v-if="$store.getters['user/isModerator']"
|
||||
>
|
||||
<awesome-icon icon="users" />User management
|
||||
</button>
|
||||
<button
|
||||
@click="openDialog('serverSettings')"
|
||||
v-if="$store.getters['user/isAdministrator']"
|
||||
>
|
||||
<awesome-icon icon="server" />Server settings
|
||||
</button>
|
||||
<button
|
||||
v-if="$store.getters.isElectron"
|
||||
@click="openDialog('dialogSettings')"
|
||||
>
|
||||
<awesome-icon icon="cog" />Settings
|
||||
</button>
|
||||
<hr />
|
||||
<button
|
||||
@click="openDialog('audioUploader')"
|
||||
v-if="!$store.getters['user/isGuest']"
|
||||
>
|
||||
<awesome-icon icon="music" />
|
||||
Upload Audio Files
|
||||
</button>
|
||||
<button
|
||||
@click="openDialog('videoUploader')"
|
||||
v-if="!$store.getters['user/isGuest']"
|
||||
>
|
||||
<awesome-icon icon="video" />
|
||||
Upload Video Files
|
||||
</button>
|
||||
<hr />
|
||||
<button @click="openDialog('dialogUserProfile')">
|
||||
<awesome-icon icon="user" />Profile
|
||||
</button>
|
||||
<button @click="logout">
|
||||
<awesome-icon icon="sign-out-alt" />Logout [{{ user.name }}]
|
||||
</button>
|
||||
</template>
|
||||
</DropDown>
|
||||
<button v-else title="Login" @click="logout">
|
||||
<awesome-icon icon="sign-in-alt" size="2x" />
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
<div id="app-body">
|
||||
<router-view v-slot="{ Component }">
|
||||
<keep-alive>
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
</router-view>
|
||||
</div>
|
||||
<Player ref="player" />
|
||||
<Users ref="dialogUsers" />
|
||||
<UserProfile ref="dialogUserProfile" />
|
||||
<ServerSettings ref="serverSettings" />
|
||||
<DesktopSettings ref="dialogSettings" />
|
||||
<VideoScreen />
|
||||
<AudioUploadDialog ref="audioUploader" />
|
||||
<VideoUploadDialog ref="videoUploader" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import VideoScreen from "./components/dialogs/VideoScreen";
|
||||
import DropDown from "./components/base-components/DropDown";
|
||||
import Users from "./components/dialogs/Users";
|
||||
import UserProfile from "./components/dialogs/UserProfile";
|
||||
import DesktopSettings from "./components/dialogs/DesktopSettings";
|
||||
import Player from "./components/Player";
|
||||
import ServerSettings from "./components/dialogs/ServerSettings";
|
||||
import AudioUploadDialog from "./components/dialogs/AudioUpload";
|
||||
import VideoUploadDialog from "./components/dialogs/VideoUpload";
|
||||
|
||||
import { mapGetters } from "vuex";
|
||||
|
||||
export default {
|
||||
name: "app",
|
||||
data() {
|
||||
return {
|
||||
searchTimer: 0,
|
||||
timeoutHandler: 0,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.login();
|
||||
this.$store.dispatch("loadClientConfigs").then(() => {
|
||||
this.$store.dispatch("system/loadLists");
|
||||
this.$store.dispatch("loadServerInfo");
|
||||
this.$store.dispatch("loadSystemSettings");
|
||||
});
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener("keydown", (e) => {
|
||||
switch (e.key) {
|
||||
case "MediaPlay":
|
||||
this.$refs.player.togglePlaying();
|
||||
break;
|
||||
case "MediaTrackNext":
|
||||
this.$store.dispatch(
|
||||
"tracks/playNextTo",
|
||||
this.$store.getters("tracks/selectedTrack")
|
||||
);
|
||||
break;
|
||||
case "MediaTrackPrevious":
|
||||
this.$store.dispatch(
|
||||
"tracks/playPrevTo",
|
||||
this.$store.getters("tracks/selectedTrack")
|
||||
);
|
||||
break;
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
pushMediaInfo(track) {
|
||||
if ("mediaSession" in navigator && track._id) {
|
||||
navigator.mediaSession.metadata = new MediaMetadata({
|
||||
title: track.title,
|
||||
artist: track.parent.artist_name,
|
||||
album: track.parent.title,
|
||||
artwork: [
|
||||
{
|
||||
src: track.parent.covers.cover32,
|
||||
sizes: "32x32",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: track.parent.covers.cover64,
|
||||
sizes: "64x64",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: track.parent.covers.cover128,
|
||||
sizes: "96x96",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: track.parent.covers.cover128,
|
||||
sizes: "128x128",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: track.parent.covers.cover128,
|
||||
sizes: "256x256",
|
||||
type: "image/png",
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
},
|
||||
gotoSearchView() {
|
||||
if (this.$route.path != "/search") {
|
||||
let q = this.$store.getters["search/term"];
|
||||
if (q != "") {
|
||||
this.$router.push("/search?q=" + q);
|
||||
} else {
|
||||
this.$router.push("/search");
|
||||
}
|
||||
}
|
||||
},
|
||||
searchTermChanged(e) {
|
||||
clearTimeout(this.searchTimer);
|
||||
this.searchTimer = setTimeout(() => {
|
||||
let v = e.srcElement.value;
|
||||
if (!v) {
|
||||
this.$store.commit("search/setTerm", v);
|
||||
if (this.$route.query.q) {
|
||||
this.$router.replace("/search");
|
||||
}
|
||||
return;
|
||||
} else if (v == this.$store.getters["search/term"]) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$store.commit("search/setTerm", v);
|
||||
this.$router.replace("/search?q=" + this.$store.getters["search/term"]);
|
||||
}, 250);
|
||||
},
|
||||
openDialog(name) {
|
||||
this.$refs[name].open();
|
||||
},
|
||||
login() {
|
||||
let hash = window.location.hash.replace("#/", "");
|
||||
if (!hash.startsWith("login") && !hash.startsWith("setup")) {
|
||||
let redirect = encodeURIComponent(hash);
|
||||
if (redirect) {
|
||||
this.$router.replace({
|
||||
path: "login",
|
||||
query: { redirect: redirect },
|
||||
});
|
||||
} else {
|
||||
this.$router.replace("login");
|
||||
}
|
||||
}
|
||||
},
|
||||
logout() {
|
||||
this.$store.dispatch("user/logout");
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
selectedTrack: "tracks/selectedTrack",
|
||||
user: "user/user",
|
||||
}),
|
||||
},
|
||||
watch: {
|
||||
selectedTrack(newVal) {
|
||||
if (newVal._id) {
|
||||
this.pushMediaInfo(newVal);
|
||||
}
|
||||
},
|
||||
},
|
||||
components: {
|
||||
Player,
|
||||
AudioUploadDialog,
|
||||
DropDown,
|
||||
ServerSettings,
|
||||
DesktopSettings,
|
||||
Users,
|
||||
UserProfile,
|
||||
VideoScreen,
|
||||
VideoUploadDialog,
|
||||
},
|
||||
};
|
||||
</script>
|
78
src/components/Album.vue
Normal file
@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<div
|
||||
:id="item._id"
|
||||
class="container album"
|
||||
@click="click"
|
||||
:title="item.title"
|
||||
v-if="type == 'default'"
|
||||
>
|
||||
<img class="albumCover shadow" :src="cover" />
|
||||
<p class="albumTitle componentTitle">{{ item.title }}</p>
|
||||
</div>
|
||||
<div v-else-if="type == 'line'" class="album-line flex-row" @click="click">
|
||||
<img class="albumCover line shadow" :src="cover" />
|
||||
<p class="albumTitle line">
|
||||
<b>{{ item.title }}</b
|
||||
><br />by<br /><b>{{ item.artist_name }}</b>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import BaseCollection from "../mixins/BaseCollection";
|
||||
|
||||
export default {
|
||||
name: "AlbumItem",
|
||||
mixins: [BaseCollection],
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: "default",
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.$route.query.id == this.item._id && this.item.tracks) {
|
||||
this.$nextTick(() => {
|
||||
this.scrollFunction();
|
||||
this.$store.dispatch("albums/selectAlbum", this.item);
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
click() {
|
||||
if (this.$route.path != "/albums" && this.$route.path != "/artists") {
|
||||
this.$store.dispatch("albums/loadAlbum", this.item._id);
|
||||
this.$router.push("/albums?id=" + this.item._id);
|
||||
} else {
|
||||
this.scrollFunction();
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
cover() {
|
||||
let res = "/static/icons/dummy/album.svg";
|
||||
if (
|
||||
this.type == "default" &&
|
||||
this.item.covers &&
|
||||
this.item.covers.cover128
|
||||
) {
|
||||
res = this.item.covers.cover128;
|
||||
} else if (
|
||||
this.type == "line" &&
|
||||
this.item.covers &&
|
||||
this.item.covers.cover64
|
||||
) {
|
||||
res = this.item.covers.cover64;
|
||||
}
|
||||
return res;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.albumTitle.line {
|
||||
max-width: initial;
|
||||
align-self: center;
|
||||
margin-left: 8px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
</style>
|
82
src/components/Artist.vue
Normal file
@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<div
|
||||
:id="item._id"
|
||||
class="container artist"
|
||||
@click="click"
|
||||
:title="item.name"
|
||||
v-if="type == 'default'"
|
||||
>
|
||||
<img class="artistCover shadow" :src="cover" />
|
||||
<p class="artistName componentTitle">
|
||||
{{ item.name }}
|
||||
</p>
|
||||
</div>
|
||||
<div v-else-if="type == 'line'" class="album-line flex-row" @click="click">
|
||||
<img class="artistCover shadow line" :src="cover" />
|
||||
<p class="artistName line">
|
||||
<b> {{ item.name }}</b>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import BaseCollection from "../mixins/BaseCollection";
|
||||
|
||||
export default {
|
||||
name: "ArtistItem",
|
||||
mixins: [BaseCollection],
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: "default",
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.$route.query.id == this.item._id && this.item.albums) {
|
||||
this.$nextTick(() => {
|
||||
this.scrollFunction();
|
||||
this.$store.dispatch("artists/selectArtist", this.item);
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
click() {
|
||||
if (this.$route.path != "/artists") {
|
||||
this.$store.dispatch("artists/loadArtist", this.item._id);
|
||||
this.$router.push("/artists?id=" + this.item._id);
|
||||
} else {
|
||||
this.scrollFunction();
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
cover() {
|
||||
let res = "/static/icons/dummy/artist.svg";
|
||||
if (
|
||||
this.type == "default" &&
|
||||
this.item.covers &&
|
||||
this.item.covers.cover256
|
||||
) {
|
||||
res = this.item.covers.cover256;
|
||||
} else if (
|
||||
this.type == "line" &&
|
||||
this.item.covers &&
|
||||
this.item.covers.cover128
|
||||
) {
|
||||
res = this.item.covers.cover128;
|
||||
}
|
||||
return res;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.artistCover.line {
|
||||
width: 128px;
|
||||
height: 64px;
|
||||
}
|
||||
.artistName.line {
|
||||
align-self: center;
|
||||
margin-left: 8px;
|
||||
}
|
||||
</style>
|
90
src/components/Box.vue
Normal file
@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<div
|
||||
:id="item._id"
|
||||
class="container box"
|
||||
@click="click"
|
||||
:title="title"
|
||||
v-if="type == 'default'"
|
||||
>
|
||||
<img class="boxCover shadow" :src="cover" />
|
||||
<p class="boxTitle componentTitle">
|
||||
{{ item.title }}
|
||||
</p>
|
||||
</div>
|
||||
<div v-else-if="type == 'line'" class="flex-row line">
|
||||
<img class="boxCover line shadow" :src="cover" />
|
||||
<p class="boxTitle line">
|
||||
<b>{{ item.title }}</b>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import BaseCollection from "../mixins/BaseCollection";
|
||||
|
||||
export default {
|
||||
name: "BoxItem",
|
||||
mixins: [BaseCollection],
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: "default",
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.$route.query.id == this.item._id && this.item.videos) {
|
||||
this.$nextTick(() => {
|
||||
this.scrollFunction();
|
||||
this.$store.dispatch("boxes/selectBox", this.item);
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
click() {
|
||||
if (this.$route.path != "/boxes") {
|
||||
this.$store.dispatch("boxes/loadBox", this.item._id);
|
||||
this.$router.push("/boxes?id=" + this.item._id);
|
||||
} else {
|
||||
this.scrollFunction();
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
cover() {
|
||||
let res = "/static/icons/dummy/box.svg";
|
||||
if (
|
||||
this.type == "default" &&
|
||||
this.item.covers &&
|
||||
this.item.covers.cover128
|
||||
) {
|
||||
res = this.item.covers.cover128;
|
||||
} else if (
|
||||
this.type == "line" &&
|
||||
this.item.covers &&
|
||||
this.item.covers.cover64
|
||||
) {
|
||||
res = this.item.covers.cover64;
|
||||
}
|
||||
return res;
|
||||
},
|
||||
title() {
|
||||
return (
|
||||
this.item.title +
|
||||
(this.item.year && this.item.year > 0
|
||||
? " (" + this.item.year + ")"
|
||||
: "")
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.boxCover.line {
|
||||
width: 64px;
|
||||
height: 90px;
|
||||
}
|
||||
.boxTitle.line {
|
||||
align-self: center;
|
||||
margin-left: 8px;
|
||||
}
|
||||
</style>
|
416
src/components/Player.vue
Normal file
@ -0,0 +1,416 @@
|
||||
<template>
|
||||
<div
|
||||
id="player"
|
||||
class="flex-column"
|
||||
v-show="selectedTrack._id || selectedRadio._id"
|
||||
>
|
||||
<input
|
||||
type="range"
|
||||
id="slider"
|
||||
min="0"
|
||||
max="100"
|
||||
step="0.1"
|
||||
v-model="selectedTrack.percent"
|
||||
@change="slideChanged"
|
||||
/>
|
||||
<div id="playerBar" class="flex-row">
|
||||
<div class="flex-row grow">
|
||||
<img
|
||||
class="cover pointer"
|
||||
:src="
|
||||
selectedTrack.parent.covers.cover64 ||
|
||||
selectedRadio.cover64 ||
|
||||
'/static/icons/dummy/album.svg'
|
||||
"
|
||||
:title="selectedTrack.parent.title"
|
||||
@click="gotoContainer"
|
||||
/>
|
||||
<div v-if="selectedTrack._id" class="flex-column">
|
||||
<b>{{ selectedTrack.title }}</b>
|
||||
from
|
||||
<b>{{ selectedTrack.parent.title }}</b>
|
||||
</div>
|
||||
</div>
|
||||
<div id="playerControls" class="flex-row center">
|
||||
<button
|
||||
@click="switchShuffle"
|
||||
title="Shuffle mode"
|
||||
v-if="selectedTrack._id"
|
||||
>
|
||||
<img
|
||||
src="static/icons/media-shuffle-dark.svg"
|
||||
v-show="$store.getters['player/shuffle']"
|
||||
class="small"
|
||||
/>
|
||||
<img
|
||||
src="static/icons/media-consecutive-dark.svg"
|
||||
v-show="$store.getters['player/shuffle'] == false"
|
||||
class="small"
|
||||
/>
|
||||
</button>
|
||||
<button @click="prevTrack" title="Back" v-if="selectedTrack._id">
|
||||
<awesome-icon icon="backward" />
|
||||
</button>
|
||||
<button @click="togglePlaying" :title="audio.paused ? 'Play' : 'Pause'">
|
||||
<awesome-icon icon="play" size="2x" v-if="audio.paused" />
|
||||
<awesome-icon icon="pause" size="2x" v-else />
|
||||
</button>
|
||||
<button @click="nextTrack" title="Forward" v-if="selectedTrack._id">
|
||||
<awesome-icon icon="forward" />
|
||||
</button>
|
||||
<button
|
||||
@click="switchRepeatType"
|
||||
title="Repeat mode"
|
||||
v-if="selectedTrack._id"
|
||||
>
|
||||
<img
|
||||
src="static/icons/media-repeat-dark.svg"
|
||||
class="small"
|
||||
v-show="$store.getters['player/repeatType'] == 'all'"
|
||||
/>
|
||||
<img
|
||||
src="static/icons/media-repeat-song-dark.svg"
|
||||
class="small"
|
||||
v-show="$store.getters['player/repeatType'] == 'one'"
|
||||
/>
|
||||
<img
|
||||
src="static/icons/media-no-repeat-dark.svg"
|
||||
class="small"
|
||||
v-show="$store.getters['player/repeatType'] == 'none'"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="flex-row ma-right hideOnMobilePortrait grow right"
|
||||
v-show="selectedTrack.title"
|
||||
>
|
||||
{{ formatedP }} | {{ formatedD }}
|
||||
</div>
|
||||
</div>
|
||||
<audio
|
||||
preload="auto"
|
||||
ref="audioControl"
|
||||
type="audio/mpeg"
|
||||
@ended="nextTrack"
|
||||
@canplay="play"
|
||||
@playing="playing"
|
||||
@durationchange="durationChanged"
|
||||
@timeupdate="timeUpdate"
|
||||
src
|
||||
></audio>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "PlayerControl",
|
||||
data() {
|
||||
return {
|
||||
audio: {},
|
||||
duration: 0,
|
||||
progress: 0,
|
||||
interval: 0,
|
||||
preConvert: false,
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.audio = this.$refs.audioControl;
|
||||
});
|
||||
this.setMediaSession();
|
||||
},
|
||||
|
||||
methods: {
|
||||
play() {
|
||||
if (this.audio.paused) {
|
||||
this.audio.play();
|
||||
}
|
||||
},
|
||||
durationChanged() {
|
||||
this.duration = this.audio.duration;
|
||||
},
|
||||
playing() {
|
||||
window.clearInterval(this.interval);
|
||||
this.interval = setInterval(() => {
|
||||
this.progress = this.audio.currentTime;
|
||||
this.selectedTrack.percent = (100 / this.duration) * this.progress;
|
||||
}, 500);
|
||||
},
|
||||
audioReset() {
|
||||
this.audio.pause();
|
||||
this.audio.src = "";
|
||||
this.$store.commit("tracks/resetSelectedTrack");
|
||||
},
|
||||
slideChanged() {
|
||||
this.audio.pause();
|
||||
this.$store.dispatch("tracks/skip");
|
||||
},
|
||||
skipToPercent(percent) {
|
||||
let was_paused = this.audio.paused;
|
||||
this.audio.pause();
|
||||
let currentTime = Math.floor((this.duration * percent) / 100);
|
||||
this.audio.currentTime = currentTime;
|
||||
this.progress = currentTime;
|
||||
if (!was_paused) {
|
||||
this.audio.play();
|
||||
}
|
||||
},
|
||||
playRadio(radio) {
|
||||
this.$store.commit("tracks/resetSelectedTrack");
|
||||
this.audio.pause();
|
||||
this.audio.src = radio.url;
|
||||
|
||||
let item = {
|
||||
id: this.selectedRadio._id,
|
||||
cover128: this.selectedRadio.cover128,
|
||||
type: "radio",
|
||||
};
|
||||
this.$store.dispatch("user/saveHistoryItem", item);
|
||||
},
|
||||
playTrack(track) {
|
||||
this.preConvert = false;
|
||||
this.$store.commit("radios/resetSelectedRadio");
|
||||
let url =
|
||||
this.$store.getters.server +
|
||||
"/api/tracks/" +
|
||||
track._id +
|
||||
"/stream/" +
|
||||
this.audioBpm;
|
||||
|
||||
this.audio.pause();
|
||||
this.audio.src = url;
|
||||
|
||||
let item = {
|
||||
id: this.currentTrackParent._id,
|
||||
type: this.currentTrackParentType,
|
||||
};
|
||||
if (item.type == "album") {
|
||||
item.title = this.currentTrackParent.title;
|
||||
item.covers = { cover128: this.currentTrackParent.covers.cover128 };
|
||||
} else {
|
||||
item.name = this.currentTrackParent.name;
|
||||
item.covers = { cover256: this.currentTrackParent.covers.cover256 };
|
||||
}
|
||||
this.$store.dispatch("user/saveHistoryItem", item);
|
||||
item = {
|
||||
id: this.selectedTrack._id,
|
||||
type: "track",
|
||||
title: this.selectedTrack.title,
|
||||
covers: { cover32: this.selectedTrack.parent.covers.cover32 },
|
||||
parent: {
|
||||
_id: this.selectedTrack.parent._id,
|
||||
title: this.selectedTrack.parent.title,
|
||||
},
|
||||
};
|
||||
this.$store.dispatch("user/saveHistoryItem", item);
|
||||
},
|
||||
nextTrack() {
|
||||
if (this.$store.getters["player/repeatType"] == "one") {
|
||||
this.skipToPercent(0);
|
||||
this.audio.play();
|
||||
} else {
|
||||
this.$store.dispatch("tracks/playNextTo", this.selectedTrack);
|
||||
}
|
||||
},
|
||||
prevTrack() {
|
||||
this.$store.dispatch("tracks/playPrevTo", this.selectedTrack);
|
||||
},
|
||||
togglePlaying() {
|
||||
if (!this.audio) {
|
||||
return;
|
||||
}
|
||||
if (!this.audio.paused) {
|
||||
this.audio.pause();
|
||||
} else if (this.audio.src != "") {
|
||||
this.audio.play();
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
window.clearInterval(this.interval);
|
||||
if (!this.audio.paused) {
|
||||
this.audio.pause();
|
||||
}
|
||||
this.audio.src = "";
|
||||
},
|
||||
setMediaSession() {
|
||||
if ("mediaSession" in navigator) {
|
||||
let me = this;
|
||||
navigator.mediaSession.setActionHandler("play", function () {
|
||||
me.togglePlaying();
|
||||
});
|
||||
navigator.mediaSession.setActionHandler("pause", function () {
|
||||
me.togglePlaying();
|
||||
});
|
||||
navigator.mediaSession.setActionHandler("seekto", function (details) {
|
||||
if (details.fastSeek && "fastSeek" in me.audio) {
|
||||
me.audio.fastSeek(details.seekTime);
|
||||
return;
|
||||
}
|
||||
me.audio.currentTime = details.seekTime;
|
||||
});
|
||||
navigator.mediaSession.setActionHandler("previoustrack", function () {
|
||||
me.prevTrack();
|
||||
});
|
||||
navigator.mediaSession.setActionHandler("nexttrack", function () {
|
||||
me.nextTrack();
|
||||
});
|
||||
}
|
||||
},
|
||||
gotoContainer() {
|
||||
switch (this.selectedTrack.parentType) {
|
||||
case "album":
|
||||
this.$router.push("/albums?id=" + this.selectedTrack.parent._id);
|
||||
break;
|
||||
case "artist":
|
||||
this.$router.push(
|
||||
"/artists?id=" + this.selectedTrack.parent.parent._id
|
||||
);
|
||||
break;
|
||||
}
|
||||
},
|
||||
switchShuffle() {
|
||||
this.$store.dispatch("player/toggleShuffleMode");
|
||||
this.saveUserSettings();
|
||||
},
|
||||
switchRepeatType() {
|
||||
this.$store.dispatch("player/switchPlayerRepeatMode");
|
||||
this.saveUserSettings();
|
||||
},
|
||||
saveUserSettings() {
|
||||
this.$store.dispatch("user/savePlayerSettings");
|
||||
},
|
||||
timeUpdate(event) {
|
||||
let percent = (event.target.currentTime / event.target.duration) * 100;
|
||||
if (percent > 10 && !this.preConvert) {
|
||||
this.preConvert = true;
|
||||
this.$store.dispatch("tracks/convertNextTo", {
|
||||
track: this.selectedTrack,
|
||||
rate: this.audioBpm,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
selectedTrack() {
|
||||
return this.$store.getters["tracks/selectedTrack"];
|
||||
},
|
||||
skipTo() {
|
||||
return this.selectedTrack.skipTo;
|
||||
},
|
||||
selectedRadio() {
|
||||
return this.$store.getters["radios/selectedRadio"];
|
||||
},
|
||||
currentTrackParent() {
|
||||
return this.$store.getters["tracks/selectedTrackContainer"];
|
||||
},
|
||||
currentTrackParentType() {
|
||||
let type = "album";
|
||||
if (
|
||||
this.selectedTrack.parent.parent &&
|
||||
this.selectedTrack.parent.parent.tracks
|
||||
) {
|
||||
type = "artist";
|
||||
}
|
||||
return type;
|
||||
},
|
||||
formatedD() {
|
||||
let m = Math.floor(this.duration / 60);
|
||||
let s = Math.floor(this.duration - m * 60);
|
||||
return (m < 10 ? "0" : "") + m + ":" + (s < 10 ? "0" : "") + s;
|
||||
},
|
||||
formatedP() {
|
||||
let m = Math.floor(this.progress / 60);
|
||||
let s = Math.floor(this.progress - m * 60);
|
||||
return (m < 10 ? "0" : "") + m + ":" + (s < 10 ? "0" : "") + s;
|
||||
},
|
||||
requestReplayTrack() {
|
||||
return this.$store.getters["player/requestReplayTrack"];
|
||||
},
|
||||
audioBpm() {
|
||||
return this.$store.getters["user/settings"].desktop_bpm || "128";
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
requestReplayTrack() {
|
||||
this.skipToPercent(0);
|
||||
},
|
||||
skipTo(newVal) {
|
||||
if (newVal) {
|
||||
this.skipToPercent(newVal);
|
||||
}
|
||||
},
|
||||
selectedRadio(newVal) {
|
||||
if (newVal._id) {
|
||||
this.playRadio(newVal);
|
||||
} else {
|
||||
this.reset();
|
||||
}
|
||||
},
|
||||
selectedTrack(newVal) {
|
||||
if (newVal._id) {
|
||||
this.playTrack(newVal);
|
||||
} else {
|
||||
this.reset();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#player {
|
||||
background-color: var(--nav);
|
||||
max-height: 60px;
|
||||
height: 60px;
|
||||
z-index: 1001;
|
||||
box-shadow: 0 0px 4px var(--shadow);
|
||||
}
|
||||
#player .cover {
|
||||
width: 52px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
#playerBar {
|
||||
overflow: hidden;
|
||||
max-height: 52px;
|
||||
}
|
||||
#playerBar > div {
|
||||
align-items: center;
|
||||
}
|
||||
#playerControls button {
|
||||
display: flex;
|
||||
padding: 4px 12px;
|
||||
align-self: stretch;
|
||||
border: none;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
#player #playerControls {
|
||||
justify-content: end;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="range"] {
|
||||
border: none;
|
||||
overflow: hidden;
|
||||
-webkit-appearance: none;
|
||||
background-color: var(--secondary);
|
||||
height: 8px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
input[type="range"]::-webkit-slider-runnable-track {
|
||||
height: 8px;
|
||||
-webkit-appearance: none;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
input[type="range"]::-webkit-slider-thumb {
|
||||
width: 10px;
|
||||
-webkit-appearance: none;
|
||||
height: 8px;
|
||||
cursor: ew-resize;
|
||||
background: var(--dark);
|
||||
box-shadow: -4000px 0 0 4000px var(--primary);
|
||||
}
|
||||
</style>
|
34
src/components/Radio.vue
Normal file
@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<div :title="item.name" class="container radio" @click="clicked">
|
||||
<img class="radioCover shadow" :src="item.cover128" />
|
||||
<p
|
||||
class="radioTitle"
|
||||
:class="{ selected: item == $store.state.selectedRadio }"
|
||||
>
|
||||
{{ item.name }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseCollection from "../mixins/BaseCollection";
|
||||
|
||||
export default {
|
||||
name: "RadioItem",
|
||||
mixins: [BaseCollection],
|
||||
mounted() {
|
||||
if (this.$route.query.play == this.item._id) {
|
||||
this.$store.dispatch("radios/play", this.item);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clicked() {
|
||||
if (this.$route.path == "/" || this.$route.path == "/search") {
|
||||
this.$router.push("/radios?play=" + this.item._id);
|
||||
} else {
|
||||
this.$store.dispatch("radios/play", this.item);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
106
src/components/Track.vue
Normal file
@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<div
|
||||
:id="track._id"
|
||||
:title="track.title"
|
||||
class="track"
|
||||
@click="clicked"
|
||||
:class="{ selected: track._id == selectedTrack._id }"
|
||||
>
|
||||
<div class="trackDetails">
|
||||
<img
|
||||
v-if="showCover == true"
|
||||
:src="cover"
|
||||
:title="track.parent.title"
|
||||
class="trackCover shadow"
|
||||
/>
|
||||
<div class="trackTitle">{{ track.title }}</div>
|
||||
<div class="trackDuration" v-if="track.duration">{{ duration }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "TrackItem",
|
||||
props: {
|
||||
track: {
|
||||
type: Object,
|
||||
},
|
||||
showCover: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.scrollFunction();
|
||||
},
|
||||
methods: {
|
||||
clicked() {
|
||||
if (this.$route.path == "/" || this.$route.path == "/search") {
|
||||
this.$store.dispatch("albums/loadAlbum", this.track.parent._id);
|
||||
this.$router.push(
|
||||
"/albums?id=" + this.track.parent._id + "&play=" + this.track._id
|
||||
);
|
||||
} else {
|
||||
this.$store.dispatch("tracks/play", this.track);
|
||||
}
|
||||
},
|
||||
scrollFunction(force) {
|
||||
if (this.track._id != this.selectedTrack._id && force !== true) {
|
||||
return;
|
||||
}
|
||||
|
||||
let tracklist = document.getElementById("trackList");
|
||||
if (!tracklist) {
|
||||
return;
|
||||
}
|
||||
let parent = tracklist.getBoundingClientRect();
|
||||
let element = document.getElementById(this.track._id);
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
let bounding = element.getBoundingClientRect();
|
||||
let scrollDown = bounding.top < parent.top;
|
||||
let scrollUp = bounding.bottom > parent.bottom;
|
||||
if (scrollDown || force) {
|
||||
this.$nextTick(() => {
|
||||
element.scrollIntoView({
|
||||
block: "start",
|
||||
});
|
||||
});
|
||||
} else if (scrollUp) {
|
||||
this.$nextTick(() => {
|
||||
element.scrollIntoView({
|
||||
block: "end",
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
duration() {
|
||||
let min = parseInt(this.track.duration / 60);
|
||||
let sec = parseInt(this.track.duration - min * 60);
|
||||
return min + ":" + (sec < 10 ? "0" : "") + sec;
|
||||
},
|
||||
selectedTrack() {
|
||||
return this.$store.getters["tracks/selectedTrack"];
|
||||
},
|
||||
cover() {
|
||||
let cover = this.track.covers ? this.track.covers.cover32 : undefined;
|
||||
if (!cover) {
|
||||
cover =
|
||||
this.track.parent.covers && this.track.parent.covers.cover64
|
||||
? this.track.parent.covers.cover64
|
||||
: "/static/icons/dummy/album.svg";
|
||||
}
|
||||
return cover;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
selectedTrack() {
|
||||
this.scrollFunction();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
65
src/components/Video.vue
Normal file
@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<div
|
||||
:id="video._id"
|
||||
class="video"
|
||||
:title="video.title"
|
||||
@click="clicked"
|
||||
:class="{ selected: video == $store.state.selectedVideo }"
|
||||
>
|
||||
<img class="videoCover shadow" :src="video.thumbnail" />
|
||||
<div class="videoTitle componentTitle">{{ video.title }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "VideoItem",
|
||||
props: {
|
||||
video: {
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clicked() {
|
||||
if (this.$route.path == "/" || this.$route.path == "/search") {
|
||||
this.$store.dispatch("boxes/loadBox", this.video.parent._id);
|
||||
this.$router.push(
|
||||
"/boxes?id=" + this.video.parent._id + "&play=" + this.video._id
|
||||
);
|
||||
} else {
|
||||
this.$store.dispatch("videos/play", this.video);
|
||||
}
|
||||
},
|
||||
scrollFunction(video) {
|
||||
if (video._id != this.video._id) {
|
||||
return;
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
let parent = document
|
||||
.getElementById("videoList")
|
||||
.getBoundingClientRect();
|
||||
let element = document.getElementById(this.video._id);
|
||||
let bounding = element.getBoundingClientRect();
|
||||
let scrollDown = bounding.top < parent.top;
|
||||
let scrollUp = bounding.bottom > parent.bottom;
|
||||
if (scrollDown) {
|
||||
element.scrollIntoView({
|
||||
block: "start",
|
||||
behavior: "smooth",
|
||||
});
|
||||
} else if (scrollUp) {
|
||||
element.scrollIntoView({
|
||||
block: "end",
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
selectedVideo() {
|
||||
return this.$store.getters["selectedVideo"];
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
496
src/components/base-components/Dialog.vue
Normal file
@ -0,0 +1,496 @@
|
||||
<template>
|
||||
<div
|
||||
ref="dialogBackground"
|
||||
class="dialog-background"
|
||||
v-if="visible"
|
||||
@click="bgClicked"
|
||||
>
|
||||
<div
|
||||
class="dialog-window"
|
||||
:class="{
|
||||
fullscreen: isFullscreen,
|
||||
'max-size': maxSize,
|
||||
playing: $store.getters['player/isPlaying'],
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="dialog-header"
|
||||
:class="{ hideOnMobile: showHeaderOnMobile == false }"
|
||||
v-if="showHeader"
|
||||
@dblclick="headerDoubleClick"
|
||||
>
|
||||
<div class="dialog-header-left">
|
||||
<slot name="header-left" />
|
||||
</div>
|
||||
<div class="dialog-header-center">
|
||||
<h3 v-if="dialogTitle">{{ dialogTitle }}</h3>
|
||||
<slot name="header-center" />
|
||||
</div>
|
||||
<div class="dialog-header-right">
|
||||
<slot name="header-right" />
|
||||
<button
|
||||
class="hideOnMobile"
|
||||
@click="isFullscreen = !isFullscreen"
|
||||
v-if="showFullscreenButton"
|
||||
:title="isFullscreen ? 'Restore' : 'Fullscreen'"
|
||||
>
|
||||
<awesome-icon v-if="isFullscreen" icon="compress" />
|
||||
<awesome-icon v-else icon="expand" />
|
||||
</button>
|
||||
<button
|
||||
@click="cancel"
|
||||
class="red"
|
||||
v-if="showCloseButton"
|
||||
title="Close"
|
||||
>
|
||||
<awesome-icon icon="times" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="dialog-body"
|
||||
:class="{
|
||||
hideXScroll: disableXscroll == true,
|
||||
hideYScroll: disableYscroll == true,
|
||||
}"
|
||||
>
|
||||
<slot />
|
||||
<div
|
||||
class="dialog-body-content"
|
||||
v-if="dialogContent"
|
||||
v-html="dialogContent"
|
||||
/>
|
||||
</div>
|
||||
<div class="dialog-footer" v-if="showFooter">
|
||||
<div class="dialog-footer-left">
|
||||
<span v-if="messageText" class="dialog-message" :class="messageClass">
|
||||
<awesome-icon v-if="messageIcon" :icon="messageIcon" />
|
||||
{{ messageText }}
|
||||
</span>
|
||||
<slot name="footer-left" />
|
||||
</div>
|
||||
<div class="dialog-footer-center">
|
||||
<slot name="footer-center" />
|
||||
</div>
|
||||
<div>
|
||||
<slot name="footer-right" />
|
||||
<div
|
||||
class="dialog-footer-controls"
|
||||
v-if="
|
||||
showFooterButtons &&
|
||||
(openButtons == null || openButtons.length > 0)
|
||||
"
|
||||
>
|
||||
<button
|
||||
v-for="(button, i) in dialogButtons"
|
||||
:key="i"
|
||||
@click="click('clicked', button.text || button)"
|
||||
:class="button.class || ''"
|
||||
:disabled="button.disabled"
|
||||
:title="button.title || ''"
|
||||
v-show="button.visible != false"
|
||||
>
|
||||
<awesome-icon
|
||||
v-if="button.icon"
|
||||
:icon="button.icon"
|
||||
class="ma-right8"
|
||||
/>
|
||||
{{ button.text || button }}
|
||||
</button>
|
||||
<button
|
||||
ref="dialogButton"
|
||||
@click="click('accept')"
|
||||
v-if="showFooterButtons && !dialogButtons"
|
||||
:disabled="!enableFooterButtons"
|
||||
:class="buttonClass"
|
||||
>
|
||||
<awesome-icon
|
||||
v-if="buttonIcon"
|
||||
:icon="buttonIcon"
|
||||
class="ma-right8"
|
||||
/>
|
||||
{{ buttonText }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: "DialogControl",
|
||||
props: {
|
||||
title: { type: String, default: "" },
|
||||
content: { type: String, default: "" },
|
||||
closeOnEscape: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
closeOnFocusLost: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
closeOnButtonClick: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
disableXscroll: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
disableYscroll: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showFooter: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showHeader: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showHeaderOnMobile: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showCloseButton: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showFooterButtons: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showFullscreenButton: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
enableFooterButtons: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
buttonText: {
|
||||
type: String,
|
||||
default: "Done",
|
||||
},
|
||||
buttonClass: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
buttonIcon: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
buttons: {
|
||||
type: Array,
|
||||
},
|
||||
maxSize: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
openTitle: "",
|
||||
openContent: "",
|
||||
openButtons: null,
|
||||
isFullscreen: false,
|
||||
callback: null,
|
||||
messageText: "",
|
||||
messageClass: "",
|
||||
messageIcon: "",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
open(title = null, content = null, buttons = null, callback = null) {
|
||||
this.visible = true;
|
||||
if (title) {
|
||||
this.openTitle = title;
|
||||
}
|
||||
if (content) {
|
||||
this.openContent = content;
|
||||
}
|
||||
if (buttons) {
|
||||
this.openButtons = buttons;
|
||||
}
|
||||
if (callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
window.addEventListener("keydown", this.keydownListener);
|
||||
this.focusButton();
|
||||
this.$emit("opened");
|
||||
},
|
||||
close() {
|
||||
this.$emit("closing");
|
||||
this.visible = false;
|
||||
window.removeEventListener("keydown", this.keydownListener);
|
||||
this.$emit("closed");
|
||||
if (this.callback) {
|
||||
this.callback();
|
||||
}
|
||||
},
|
||||
cancel() {
|
||||
this.$emit("canceled");
|
||||
this.close();
|
||||
},
|
||||
focusButton() {
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.dialogButton) {
|
||||
this.$refs.dialogButton.focus();
|
||||
}
|
||||
});
|
||||
},
|
||||
click(name, parameter = null) {
|
||||
this.$emit(name, parameter);
|
||||
if (this.callback) {
|
||||
this.callback(parameter || name);
|
||||
this.callback = undefined;
|
||||
}
|
||||
if (this.closeOnButtonClick) {
|
||||
this.close();
|
||||
}
|
||||
},
|
||||
bgClicked(e) {
|
||||
if (
|
||||
e.srcElement == this.$refs.dialogBackground &&
|
||||
this.closeOnFocusLost
|
||||
) {
|
||||
this.cancel();
|
||||
}
|
||||
},
|
||||
headerDoubleClick() {
|
||||
if (this.showFullscreenButton) {
|
||||
this.isFullscreen = !this.isFullscreen;
|
||||
}
|
||||
},
|
||||
keydownListener(e) {
|
||||
if (e.key == "Escape" && this.closeOnEscape) {
|
||||
e.preventDefault();
|
||||
this.cancel();
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
dialogTitle() {
|
||||
return this.openTitle || this.title;
|
||||
},
|
||||
dialogButtons() {
|
||||
return this.openButtons || this.buttons;
|
||||
},
|
||||
dialogContent() {
|
||||
return this.openContent || this.content;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.dialog-background {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #00000080;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1000;
|
||||
animation: fadeIn ease 0.15s;
|
||||
}
|
||||
.dialog-window {
|
||||
box-shadow: 0px 8px 32px var(--shadow);
|
||||
background-color: var(--white);
|
||||
max-width: 90%;
|
||||
max-height: 80%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
.dialog-window.max-size {
|
||||
width: 90%;
|
||||
height: 90%;
|
||||
}
|
||||
.dialog-window.fullscreen {
|
||||
max-width: initial;
|
||||
max-height: initial;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-top-left-radius: 0px;
|
||||
border-top-right-radius: 0px;
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
background-color: var(--background);
|
||||
box-shadow: 0px 1px 4px var(--shadow);
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
z-index: 1;
|
||||
}
|
||||
.dialog-header h3 {
|
||||
flex-grow: 1;
|
||||
margin: 0;
|
||||
font-size: 0.9rem;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
align-self: center;
|
||||
cursor: default;
|
||||
}
|
||||
.dialog-header input,
|
||||
.dialog-header select {
|
||||
padding: 0 4px;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background-color: var(--white);
|
||||
align-self: stretch;
|
||||
}
|
||||
.dialog-header button,
|
||||
.dialog-header a {
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
border-radius: 16px;
|
||||
padding: 4px;
|
||||
margin: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dialog-header button:hover {
|
||||
background-color: var(--light-gray);
|
||||
}
|
||||
.dialog-header button.red:hover {
|
||||
background-color: var(--red50);
|
||||
color: var(--white);
|
||||
}
|
||||
.dialog-header button.red svg {
|
||||
color: var(--red);
|
||||
}
|
||||
.dialog-header button.red:hover svg {
|
||||
color: var(--white);
|
||||
}
|
||||
.dialog-header button.blue {
|
||||
background-color: var(--blue);
|
||||
}
|
||||
.dialog-header button.green:hover {
|
||||
background-color: var(--green);
|
||||
color: var(--green);
|
||||
}
|
||||
.dialog-header button.green svg {
|
||||
color: var(--green);
|
||||
}
|
||||
.dialog-header button.yellow {
|
||||
background-color: var(--yellow);
|
||||
}
|
||||
/* SUCCESS */
|
||||
.dialog-header button.success svg {
|
||||
color: var(--success);
|
||||
}
|
||||
.dialog-header button.success:hover {
|
||||
background-color: var(--success);
|
||||
}
|
||||
.dialog-header button.success:hover svg {
|
||||
color: var(--white);
|
||||
}
|
||||
.dialog-header button.primary,
|
||||
.dialog-header a.primary {
|
||||
background-color: var(--primary);
|
||||
color: var(--white);
|
||||
}
|
||||
.dialog-header button > span,
|
||||
.dialog-header a > span {
|
||||
margin-left: 12px;
|
||||
}
|
||||
.dialog-header svg {
|
||||
width: 16px !important;
|
||||
height: 16px;
|
||||
}
|
||||
.dialog-header > div,
|
||||
.dialog-footer > div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.dialog-header > .dialog-header-center,
|
||||
.dialog-footer > .dialog-footer-center {
|
||||
flex-grow: 1;
|
||||
justify-content: center;
|
||||
margin: 0 12px;
|
||||
}
|
||||
.dialog-header > .dialog-header-right input,
|
||||
.dialog-header > .dialog-header-right select {
|
||||
border-left: 1px solid var(--light-border);
|
||||
}
|
||||
|
||||
.dialog-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.dialog-body.hideXScroll {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.dialog-body.hideYScroll {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
.dialog-body-content {
|
||||
margin: 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.dialog-footer {
|
||||
border-top: 1px solid var(--light-border);
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
background-color: var(--background);
|
||||
}
|
||||
.dialog-footer button:disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.dialog-footer .dialog-message {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.dialog-footer .dialog-message svg {
|
||||
margin-right: 4px;
|
||||
}
|
||||
.dialog-footer .dialog-footer-controls {
|
||||
padding: 4px;
|
||||
display: flex;
|
||||
}
|
||||
.dialog-footer .dialog-footer-controls button:not(:last-child) {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
@media (max-width: 480px), (max-height: 480px) {
|
||||
.dialog-window {
|
||||
max-width: initial;
|
||||
max-height: initial;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
.dialog-window.max-size {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.dialog-header {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
.dialog-window.playing {
|
||||
margin-bottom: 60px;
|
||||
height: calc(100% - 60px);
|
||||
}
|
||||
}
|
||||
</style>
|
64
src/components/base-components/DropDown.vue
Normal file
@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div class="dropdown">
|
||||
<div ref="dropdownActivator" class="dropdown-activator" @click="click">
|
||||
<slot />
|
||||
</div>
|
||||
<div v-if="open" class="dropdown-background" ref="dropdownBackground" @click="bgClicked">
|
||||
<div
|
||||
ref="dropdownContent"
|
||||
class="dropdown-content"
|
||||
:style="'top: ' + top + 'px; left: ' + left + 'px;'"
|
||||
>
|
||||
<slot name="dropdown-content" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
closeOnClick: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
open: false,
|
||||
top: 0,
|
||||
left: 0
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
click() {
|
||||
let a = this.$refs.dropdownActivator.getBoundingClientRect();
|
||||
let x = a.left;
|
||||
let y = a.top + a.height;
|
||||
let h = window.innerHeight;
|
||||
let w = window.innerWidth;
|
||||
|
||||
this.open = !this.open;
|
||||
if (this.open) {
|
||||
this.$nextTick(() => {
|
||||
let b = this.$refs.dropdownContent.getBoundingClientRect();
|
||||
if (x + b.width > w) {
|
||||
x -= b.width - a.width;
|
||||
}
|
||||
if (y + b.height > h) {
|
||||
y -= b.height + a.height;
|
||||
}
|
||||
this.left = x;
|
||||
this.top = y;
|
||||
});
|
||||
}
|
||||
},
|
||||
bgClicked(e) {
|
||||
if (!this.closeOnClick && e.srcElement != this.$refs.dropdownBackground) {
|
||||
return;
|
||||
}
|
||||
this.open = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
33
src/components/base-components/Expander.vue
Normal file
@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<div class="expander">
|
||||
<div class="expander-header" @click="expanded = !expanded">
|
||||
<awesome-icon icon="caret-up" size="2x" v-if="expanded" />
|
||||
<awesome-icon icon="caret-down" size="2x" v-else />
|
||||
<h3>{{title}}</h3>
|
||||
</div>
|
||||
<div class="expander-body" v-show="expanded">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name:"ExpanderControl",
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
expand: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
expanded: false
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
79
src/components/base-components/MessageScreen.vue
Normal file
@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<div id="message-screen">
|
||||
<div class="ma-left ma-right">
|
||||
<div id="message-screen-header">
|
||||
<img v-if="img" :src="img" />
|
||||
<awesome-icon v-if="icon" :icon="icon" />
|
||||
<div>
|
||||
<h1>{{ title }}</h1>
|
||||
<h2>{{ subtitle }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div id="message-screen-body" v-if="showCommands">
|
||||
<div
|
||||
class="message-screen-command"
|
||||
:class="{ 'margin-image': img || icon }"
|
||||
v-for="(command, index) in commands"
|
||||
:key="'Command-' + index"
|
||||
@click="$emit('commandClicked', command)"
|
||||
>
|
||||
<awesome-icon v-if="command.icon" size="2x" :icon="command.icon" />
|
||||
<img v-if="command.img" :src="command.img" />
|
||||
<div>
|
||||
<h3>{{ command.title }}</h3>
|
||||
<span>{{ command.subtitle }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<ul
|
||||
class="message-screen-list"
|
||||
:class="{ 'margin-image': img || icon }"
|
||||
>
|
||||
<li
|
||||
v-for="(item, index) in list"
|
||||
:key="'ListItem-' + index"
|
||||
@click="$emit('listClicked', item)"
|
||||
>
|
||||
{{ listProperty != "" ? item[listProperty] : item }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
commands: {
|
||||
type: Array,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
img: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
list: {
|
||||
type: Array,
|
||||
},
|
||||
listProperty: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
subtitle: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
showCommands: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
113
src/components/dialogs/AlbumMerge.vue
Normal file
@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<DialogBase
|
||||
ref="dialogWindow"
|
||||
title="Move all title..."
|
||||
buttonText="Move"
|
||||
buttonClass="accept"
|
||||
@accept="merge"
|
||||
@closed="closed"
|
||||
:closeOnButtonClick="false"
|
||||
:enableFooterButtons="acceptable"
|
||||
>
|
||||
<div class="flex-row" id="merge-content">
|
||||
<div class="flex-column">
|
||||
<h4 class="ma-left ma-top">From</h4>
|
||||
<AlbumItem class="ma" :item="source" />
|
||||
</div>
|
||||
<awesome-icon id="arrow-icon" icon="arrow-right" />
|
||||
<div class="flex-column">
|
||||
<DropDown class="ma-left ma-top">
|
||||
<input
|
||||
type="search"
|
||||
v-model="search"
|
||||
@search="searchTermChanged"
|
||||
placeholder="Into"
|
||||
/>
|
||||
<template v-slot:dropdown-content>
|
||||
<div class="flex-column">
|
||||
<h3 class="ma" v-if="albums.length == 0">Enter an album title</h3>
|
||||
<AlbumItem
|
||||
class="ma8"
|
||||
v-for="album in albums"
|
||||
:key="album._id"
|
||||
:item="album"
|
||||
@click="select(album)"
|
||||
type="line"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</DropDown>
|
||||
<AlbumItem class="ma" :item="target" />
|
||||
</div>
|
||||
</div>
|
||||
</DialogBase>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
albums: [],
|
||||
search: "",
|
||||
searchTimer: 0,
|
||||
source: {},
|
||||
target: { covers: {} },
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
closed() {
|
||||
this.albums = [];
|
||||
this.source = {};
|
||||
this.target = { covers: {} };
|
||||
this.search = "";
|
||||
clearTimeout(this.searchTimer);
|
||||
},
|
||||
open(source) {
|
||||
this.source = source;
|
||||
this.$refs.dialogWindow.open();
|
||||
},
|
||||
merge() {
|
||||
this.$store
|
||||
.dispatch("albums/move", {
|
||||
source: this.source._id,
|
||||
target: this.target._id,
|
||||
})
|
||||
.then(() => {
|
||||
this.$store.dispatch("albums/loadAlbum", this.target._id);
|
||||
this.$store.dispatch("albums/selectAlbumById", this.target._id);
|
||||
this.$store.dispatch("albums/remove", this.source._id);
|
||||
this.$refs.dialogWindow.close();
|
||||
});
|
||||
},
|
||||
searchTermChanged() {
|
||||
clearTimeout(this.searchTimer);
|
||||
this.searchTimer = setTimeout(() => {
|
||||
this.$store.dispatch("albums/filter", this.search).then((result) => {
|
||||
this.albums = result;
|
||||
});
|
||||
}, 250);
|
||||
},
|
||||
select(album) {
|
||||
this.target = album;
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
acceptable() {
|
||||
return this.source._id != undefined && this.target._id != undefined;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
input {
|
||||
width: 128px;
|
||||
}
|
||||
#arrow-icon {
|
||||
align-self: center;
|
||||
}
|
||||
#merge-content {
|
||||
align-items: flex-end;
|
||||
}
|
||||
</style>
|
494
src/components/dialogs/AlbumViewer.vue
Normal file
@ -0,0 +1,494 @@
|
||||
<template>
|
||||
<DialogBase
|
||||
ref="dialogWindow"
|
||||
:title="album_title"
|
||||
@canceled="closed"
|
||||
@opened="opened"
|
||||
:showFooter="false"
|
||||
:disableXscroll="true"
|
||||
:disableYscroll="true"
|
||||
>
|
||||
<div id="albumViewer">
|
||||
<div id="header" class="flex-column">
|
||||
<div id="background" :style="coverBackground" />
|
||||
|
||||
<div id="albumList" class="flex-row z1" @scroll="loadingAlbums()">
|
||||
<div class="dummyAlbum" />
|
||||
<div id="loadPrevAlbums" />
|
||||
|
||||
<AlbumItem
|
||||
v-for="album in albums"
|
||||
:key="album._id"
|
||||
:item="album"
|
||||
class="ma"
|
||||
:class="{ focus: album._id == selectedAlbum._id }"
|
||||
:id="album._id"
|
||||
@touchend="albumAutoSelect"
|
||||
@click="selectAlbum(album)"
|
||||
@dblclick="dblclick"
|
||||
/>
|
||||
|
||||
<div id="loadNextAlbums" />
|
||||
<div class="dummyAlbum" />
|
||||
</div>
|
||||
<awesome-icon
|
||||
icon="star"
|
||||
size="2x"
|
||||
class="favourite ma4"
|
||||
:class="{ active: isFavourite }"
|
||||
@click="toggleFavourite"
|
||||
/>
|
||||
<div id="stats" class="flex-row grow z1 pa4-bottom">
|
||||
<DropDown v-if="$store.getters['user/isAdministrator']">
|
||||
<button class="flat pa8-left pa8-right" :title="visibility_text">
|
||||
<awesome-icon :icon="visibility_icon" />
|
||||
</button>
|
||||
<template v-slot:dropdown-content>
|
||||
<div>
|
||||
<button
|
||||
v-for="(item, i) in $store.state.system.lists.visibility"
|
||||
:key="i"
|
||||
@click="setVisibility(item)"
|
||||
>
|
||||
<awesome-icon :icon="getVisibilityIcon(item)" />{{
|
||||
getVisibilityText(item)
|
||||
}}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</DropDown>
|
||||
|
||||
<span class="grow center" @click="scrollIntoCenter(selectedAlbum)">
|
||||
<b>{{ album_title }}</b> by
|
||||
<b @click="gotoArtist" class="pointer">{{
|
||||
selectedAlbum.artist_name
|
||||
}}</b>
|
||||
<br />
|
||||
<span v-if="album_year">
|
||||
from year <b>{{ album_year }}</b> </span
|
||||
><br />
|
||||
<b>{{ album_tracks.length }}</b> Tracks with a duration of
|
||||
<b>{{ album_duration }}</b>
|
||||
</span>
|
||||
|
||||
<DropDown v-if="$store.getters['user/isAdministrator']">
|
||||
<button class="flat pa8-left pa8-right">
|
||||
<awesome-icon icon="ellipsis-v" />
|
||||
</button>
|
||||
<template v-slot:dropdown-content>
|
||||
<div>
|
||||
<button @click="uploadNewCover">
|
||||
<awesome-icon icon="image" />Set new Cover...
|
||||
</button>
|
||||
<button @click="resetCover">
|
||||
<awesome-icon icon="eraser" />Reset Cover
|
||||
</button>
|
||||
<hr />
|
||||
<button @click="mergeAlbum">
|
||||
<awesome-icon icon="compress-alt" />Merge Albums...
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</DropDown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul id="trackList" class="tracks">
|
||||
<li v-for="track in selectedAlbum.tracks" :key="track._id">
|
||||
<TrackItem :track="track" :showCover="false" />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<AlbumMerge ref="mergeDialog" />
|
||||
</DialogBase>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AlbumMerge from "./AlbumMerge.vue";
|
||||
import TrackItem from "../Track";
|
||||
import { mapGetters } from "vuex";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
move: 152,
|
||||
albums: [],
|
||||
scrollTimer: 0,
|
||||
loadingPrev: false,
|
||||
loadingNext: false,
|
||||
elements: {},
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
if (window.innerWidth <= 480 || window.innerHeight <= 480) {
|
||||
this.move = 120;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
albumAutoSelect() {
|
||||
this.albums.forEach((album) => {
|
||||
let center_client = document.documentElement.clientWidth / 2;
|
||||
let e = document.getElementById(album._id);
|
||||
let r = e.getBoundingClientRect();
|
||||
let center_element = center_client - r.left - r.width / 2;
|
||||
if (center_element < this.move / 2 && center_element > -this.move / 2) {
|
||||
if (album._id == this.selectedAlbum._id) {
|
||||
this.scrollIntoCenter(album, "smooth");
|
||||
} else {
|
||||
this.selectAlbum(album);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
readElements() {
|
||||
if (document.getElementById("header") == undefined) {
|
||||
return false;
|
||||
}
|
||||
this.elements.prev = document
|
||||
.getElementById("loadPrevAlbums")
|
||||
.getBoundingClientRect();
|
||||
|
||||
this.elements.next = document
|
||||
.getElementById("loadNextAlbums")
|
||||
.getBoundingClientRect();
|
||||
|
||||
this.elements.header = document
|
||||
.getElementById("header")
|
||||
.getBoundingClientRect();
|
||||
|
||||
this.elements.albums = document.getElementById("albumList");
|
||||
|
||||
return true;
|
||||
},
|
||||
dblclick() {
|
||||
this.$store.commit("tracks/resetSelectedTrack");
|
||||
this.$store.commit("radios/resetSelectedRadio");
|
||||
this.$store.dispatch("tracks/playContainer", this.selectedAlbum);
|
||||
},
|
||||
gotoArtist() {
|
||||
let artist = this.$store.getters["artists/collection"].find(
|
||||
(f) => f._id == this.selectedAlbum.artist_id
|
||||
);
|
||||
if (artist) {
|
||||
this.$store.dispatch("artists/selectArtist", artist);
|
||||
} else {
|
||||
this.$store
|
||||
.dispatch("artists/loadArtist", this.selectedAlbum.artist_id)
|
||||
.then((artist) => {
|
||||
this.$store.dispatch("artists/selectArtist", artist);
|
||||
});
|
||||
}
|
||||
},
|
||||
gotoNextAlbum() {
|
||||
let i = this.albums.indexOf(this.selectedAlbum);
|
||||
if (i < this.albums.length - 1) {
|
||||
this.selectAlbum(this.albums[++i]);
|
||||
}
|
||||
},
|
||||
gotoPrevAlbum() {
|
||||
let i = this.albums.indexOf(this.selectedAlbum);
|
||||
if (i > 0) {
|
||||
this.selectAlbum(this.albums[--i]);
|
||||
}
|
||||
},
|
||||
gotoTrack() {
|
||||
if (this.$route.query.play) {
|
||||
let track = this.selectedAlbum.tracks.find(
|
||||
(f) => f._id == this.$route.query.play
|
||||
);
|
||||
if (track) {
|
||||
this.$store.dispatch("tracks/play", track);
|
||||
}
|
||||
}
|
||||
},
|
||||
closed() {
|
||||
if (
|
||||
(window.history.state.back &&
|
||||
window.history.state.back.indexOf("?") == -1) ||
|
||||
window.history.state.back.startsWith("/search")
|
||||
) {
|
||||
this.$router.back();
|
||||
} else {
|
||||
this.$store.dispatch("albums/resetSelectedAlbum");
|
||||
}
|
||||
this.albums = [];
|
||||
},
|
||||
keydownListener(e) {
|
||||
if (e.key == "ArrowLeft") {
|
||||
e.preventDefault();
|
||||
this.elements.albums.scrollLeft -= 0;
|
||||
this.gotoPrevAlbum();
|
||||
}
|
||||
if (e.key == "ArrowRight") {
|
||||
e.preventDefault();
|
||||
this.elements.albums.scrollLeft += 0;
|
||||
this.gotoNextAlbum();
|
||||
}
|
||||
},
|
||||
loadingAlbums() {
|
||||
clearTimeout(this.scrollTimer);
|
||||
if (!this.readElements()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let posPrev = this.elements.prev.left - this.elements.header.left;
|
||||
let posNext = this.elements.header.right - this.elements.next.right;
|
||||
|
||||
if (posPrev >= -this.move && !this.loadingPrev) {
|
||||
this.loadingPrev = true;
|
||||
this.$store
|
||||
.dispatch("albums/getPrevTo", this.albums[0])
|
||||
.then((album) => {
|
||||
if (album) {
|
||||
this.albums.unshift(album);
|
||||
this.elements.albums.scrollLeft += this.move;
|
||||
}
|
||||
this.loadingPrev = false;
|
||||
});
|
||||
} else if (posPrev < -this.move * 3 && !this.loadingPrev) {
|
||||
this.loadingPrev = true;
|
||||
this.elements.albums.scrollLeft -= this.move;
|
||||
this.albums.shift();
|
||||
this.loadingPrev = false;
|
||||
}
|
||||
|
||||
if (posNext >= -this.move && !this.loadingNext) {
|
||||
this.loadingNext = true;
|
||||
this.$store
|
||||
.dispatch("albums/getNextTo", this.albums[this.albums.length - 1])
|
||||
.then((album) => {
|
||||
if (album) {
|
||||
this.albums.push(album);
|
||||
}
|
||||
this.loadingNext = false;
|
||||
});
|
||||
} else if (posNext < -this.move * 3 && !this.loadingNext) {
|
||||
this.loadingNext = true;
|
||||
this.albums.pop();
|
||||
this.loadingNext = false;
|
||||
}
|
||||
},
|
||||
mergeAlbum() {
|
||||
this.$refs.mergeDialog.open(this.selectedAlbum);
|
||||
},
|
||||
opened() {
|
||||
this.$nextTick(() => {
|
||||
this.scrollIntoCenter(this.selectedAlbum);
|
||||
});
|
||||
},
|
||||
setVisibility(visibility) {
|
||||
this.selectedAlbum.visibility = visibility;
|
||||
this.$store.dispatch("albums/updateAlbum", this.selectedAlbum);
|
||||
},
|
||||
toggleFavourite() {
|
||||
this.$store.dispatch("user/toggleFavourite", {
|
||||
itemId: this.selectedAlbum._id,
|
||||
type: "album",
|
||||
});
|
||||
},
|
||||
uploadNewCover() {
|
||||
this.$store.dispatch("albums/uploadNewCover", this.selectedAlbum);
|
||||
},
|
||||
resetCover() {
|
||||
this.$store.dispatch("albums/resetCover", this.selectedAlbum);
|
||||
},
|
||||
getVisibilityIcon(visibility) {
|
||||
return visibility == "global"
|
||||
? "globe"
|
||||
: visibility == "instance"
|
||||
? "server"
|
||||
: visibility == "hidden"
|
||||
? "eye-slash"
|
||||
: "user";
|
||||
},
|
||||
getVisibilityText(visibility) {
|
||||
return visibility == "global"
|
||||
? "Global"
|
||||
: visibility == "instance"
|
||||
? "On this server"
|
||||
: visibility == "hidden"
|
||||
? "Hide this Album"
|
||||
: "Only for me";
|
||||
},
|
||||
scrollIntoCenter(album, behavior = "auto") {
|
||||
let e = document.getElementById(album._id);
|
||||
if (e) {
|
||||
e.scrollIntoView({ behavior: behavior, inline: "center" });
|
||||
}
|
||||
},
|
||||
selectAlbum(album) {
|
||||
this.$store.dispatch("albums/selectAlbum", album);
|
||||
this.scrollIntoCenter(album);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
prevAlbum: ["albums/prevAlbum"],
|
||||
nextAlbum: ["albums/nextAlbum"],
|
||||
selectedAlbum: ["albums/selectedAlbum"],
|
||||
selectedTrack: ["tracks/selectedTrack"],
|
||||
favourites: ["user/favourites"],
|
||||
}),
|
||||
album_title() {
|
||||
return this.selectedAlbum.title;
|
||||
},
|
||||
album_year() {
|
||||
return this.selectedAlbum.year;
|
||||
},
|
||||
album_tracks() {
|
||||
return this.selectedAlbum.tracks || [];
|
||||
},
|
||||
album_duration() {
|
||||
if (!this.selectedAlbum.tracks) {
|
||||
return 0;
|
||||
}
|
||||
let duration = 0;
|
||||
let hours = 0;
|
||||
let minutes = 0;
|
||||
let seconds = 0;
|
||||
|
||||
this.selectedAlbum.tracks.forEach((track) => {
|
||||
duration += track.duration;
|
||||
});
|
||||
|
||||
if (duration >= 3600) {
|
||||
hours = parseInt(duration / 3600);
|
||||
duration -= hours * 3600;
|
||||
}
|
||||
|
||||
minutes = parseInt(duration / 60);
|
||||
seconds = parseInt(duration - minutes * 60);
|
||||
return (
|
||||
(hours > 0 ? hours + ":" : "") +
|
||||
(minutes < 10 ? "0" : "") +
|
||||
minutes +
|
||||
":" +
|
||||
(seconds < 10 ? "0" : "") +
|
||||
seconds
|
||||
);
|
||||
},
|
||||
|
||||
coverBackground() {
|
||||
return "background-image: url('" + this.cover + "')";
|
||||
},
|
||||
cover() {
|
||||
return (
|
||||
this.selectedAlbum.covers.cover256 || "/static/icons/dummy/album.svg"
|
||||
);
|
||||
},
|
||||
|
||||
visibility_icon() {
|
||||
return this.selectedAlbum.visibility == "global"
|
||||
? "globe"
|
||||
: this.selectedAlbum.visibility == "instance"
|
||||
? "server"
|
||||
: this.selectedAlbum.visibility == "hidden"
|
||||
? "eye-slash"
|
||||
: "user";
|
||||
},
|
||||
visibility_text() {
|
||||
return this.selectedAlbum.visibility == "global"
|
||||
? "Visible for the whole world"
|
||||
: this.selectedAlbum.visibility == "instance"
|
||||
? "Visible on this instance"
|
||||
: this.selectedAlbum.visibility == "hidden"
|
||||
? "Hidden for all users"
|
||||
: "Visible only for me";
|
||||
},
|
||||
isFavourite() {
|
||||
return (
|
||||
this.favourites.find((f) => f.itemId == this.selectedAlbum._id) !=
|
||||
undefined
|
||||
);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
selectedAlbum(newVal) {
|
||||
if (newVal._id) {
|
||||
if (this.albums.length == 0) {
|
||||
this.albums.push(newVal);
|
||||
}
|
||||
if (!this.$refs.dialogWindow.visible) {
|
||||
this.$refs.dialogWindow.open();
|
||||
window.addEventListener("keydown", this.keydownListener);
|
||||
}
|
||||
this.gotoTrack();
|
||||
} else {
|
||||
if (this.$refs.dialogWindow.visible) {
|
||||
this.$refs.dialogWindow.close();
|
||||
}
|
||||
window.removeEventListener("keydown", this.keydownListener);
|
||||
}
|
||||
},
|
||||
},
|
||||
components: {
|
||||
AlbumMerge,
|
||||
TrackItem,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#albumViewer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
#albumList {
|
||||
overflow-x: overlay;
|
||||
max-width: 100%;
|
||||
align-self: center;
|
||||
}
|
||||
#albumList::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
#header {
|
||||
position: relative;
|
||||
min-width: 280px;
|
||||
width: 456px;
|
||||
background: black;
|
||||
}
|
||||
#header img {
|
||||
align-self: center;
|
||||
}
|
||||
#navigation {
|
||||
width: 456px;
|
||||
overflow: auto;
|
||||
}
|
||||
#stats {
|
||||
z-index: 2;
|
||||
color: var(--white);
|
||||
text-shadow: 0 1px 2px black;
|
||||
line-height: 1.4;
|
||||
background-color: #ffffff40;
|
||||
border-top: 1px solid #ffffff20;
|
||||
border-bottom: 1px solid #00000020;
|
||||
}
|
||||
#trackList {
|
||||
height: 360px;
|
||||
width: 456px;
|
||||
background-color: var(--white);
|
||||
z-index: 1;
|
||||
}
|
||||
.album {
|
||||
transition: transform 0.25s;
|
||||
}
|
||||
.album.focus {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
.dummyAlbum {
|
||||
min-width: 160px;
|
||||
}
|
||||
@media (max-width: 480px), (max-height: 480px) {
|
||||
#header {
|
||||
width: 100%;
|
||||
}
|
||||
#trackList {
|
||||
height: initial;
|
||||
width: 100%;
|
||||
}
|
||||
.dummyAlbum {
|
||||
min-width: 128px;
|
||||
}
|
||||
}
|
||||
</style>
|
115
src/components/dialogs/ArtistMerge.vue
Normal file
@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<DialogBase
|
||||
ref="dialogWindow"
|
||||
title="Move all albums..."
|
||||
buttonText="Move"
|
||||
buttonClass="accept"
|
||||
@accept="merge"
|
||||
@closed="closed"
|
||||
:closeOnButtonClick="false"
|
||||
:enableFooterButtons="acceptable"
|
||||
>
|
||||
<div class="flex-row" id="merge-content">
|
||||
<div class="flex-column">
|
||||
<h4 class="ma-left ma-top">From</h4>
|
||||
<ArtistItem class="ma" :item="source" />
|
||||
</div>
|
||||
<awesome-icon id="arrow-icon" icon="arrow-right" />
|
||||
<div class="flex-column">
|
||||
<DropDown class="ma-left ma-top">
|
||||
<input
|
||||
type="search"
|
||||
v-model="search"
|
||||
@search="searchTermChanged"
|
||||
placeholder="Into"
|
||||
/>
|
||||
<template v-slot:dropdown-content>
|
||||
<div class="flex-column">
|
||||
<h3 class="ma" v-if="artists.length == 0">
|
||||
Enter an artist name
|
||||
</h3>
|
||||
<ArtistItem
|
||||
class="ma8"
|
||||
v-for="artist in artists"
|
||||
:key="artist._id"
|
||||
:item="artist"
|
||||
@click="select(artist)"
|
||||
type="line"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</DropDown>
|
||||
<ArtistItem class="ma" :item="target" />
|
||||
</div>
|
||||
</div>
|
||||
</DialogBase>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
artists: [],
|
||||
search: "",
|
||||
searchTimer: 0,
|
||||
source: {},
|
||||
target: { covers: {} },
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
closed() {
|
||||
this.artists = [];
|
||||
this.source = {};
|
||||
this.target = { covers: {} };
|
||||
this.search = "";
|
||||
clearTimeout(this.searchTimer);
|
||||
},
|
||||
open(source) {
|
||||
this.source = source;
|
||||
this.$refs.dialogWindow.open();
|
||||
},
|
||||
merge() {
|
||||
this.$store
|
||||
.dispatch("artists/move", {
|
||||
source: this.source._id,
|
||||
target: this.target._id,
|
||||
})
|
||||
.then(() => {
|
||||
this.$store.dispatch("artists/loadArtist", this.target._id);
|
||||
this.$store.dispatch("artists/selectArtistById", this.target._id);
|
||||
this.$store.dispatch("artists/remove", this.source._id);
|
||||
this.$refs.dialogWindow.close();
|
||||
});
|
||||
},
|
||||
searchTermChanged() {
|
||||
clearTimeout(this.searchTimer);
|
||||
this.searchTimer = setTimeout(() => {
|
||||
this.$store.dispatch("artists/filter", this.search).then((result) => {
|
||||
this.artists = result;
|
||||
});
|
||||
}, 250);
|
||||
},
|
||||
select(album) {
|
||||
this.target = album;
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
acceptable() {
|
||||
return this.source._id != undefined && this.target._id != undefined;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
input {
|
||||
width: 256px;
|
||||
}
|
||||
#arrow-icon {
|
||||
align-self: center;
|
||||
}
|
||||
#merge-content {
|
||||
align-items: flex-end;
|
||||
}
|
||||
</style>
|
371
src/components/dialogs/ArtistViewer.vue
Normal file
@ -0,0 +1,371 @@
|
||||
<template>
|
||||
<DialogBase
|
||||
ref="dialogWindow"
|
||||
id="dialogWindow"
|
||||
:title="selectedArtist.name"
|
||||
@canceled="closed"
|
||||
:showFooter="false"
|
||||
:showFullscreenButton="true"
|
||||
:disableXscroll="true"
|
||||
:disableYscroll="true"
|
||||
>
|
||||
<div id="artistViewer">
|
||||
<div id="header" class="flex-column">
|
||||
<div id="background" :style="coverBackground" />
|
||||
<awesome-icon
|
||||
icon="star"
|
||||
size="2x"
|
||||
class="favourite ma4"
|
||||
:class="{ active: isFavourite }"
|
||||
@click="toggleFavourite"
|
||||
/>
|
||||
<h1 @dblclick="dblclick" class="hideOnMobileLandscape">
|
||||
{{ selectedArtist.name }}
|
||||
</h1>
|
||||
<span id="stats" class="hideOnMobileLandscape ma-bottom">
|
||||
<b>{{ artist_tracks.length }}</b> Tracks in
|
||||
<b>{{ artist_albums.length }}</b> Albums with a duration of
|
||||
<b>{{ artist_duration }}</b>
|
||||
</span>
|
||||
<div id="albumList" class="flex-row showOnMobile">
|
||||
<AlbumItem
|
||||
class="ma"
|
||||
:class="{ playing: playingAlbumId == album._id }"
|
||||
v-for="album in selectedArtist.albums"
|
||||
:key="album._id"
|
||||
:item="album"
|
||||
@click="scrollToAlbum(album)"
|
||||
@dblclick="playAlbum(album)"
|
||||
/>
|
||||
</div>
|
||||
<div id="navigation" class="flex-row center">
|
||||
<div class="flex-row grow"></div>
|
||||
<div class="flex-row">
|
||||
<button
|
||||
@click="gotoPrevArtist"
|
||||
class="primary ma4"
|
||||
:title="prevArtist.name"
|
||||
:disabled="!prevArtist._id"
|
||||
>
|
||||
<awesome-icon icon="angle-left" class="ma4" />
|
||||
</button>
|
||||
<button
|
||||
@click="gotoNextArtist"
|
||||
class="primary ma4"
|
||||
:title="nextArtist.name"
|
||||
:disabled="!nextArtist._id"
|
||||
>
|
||||
<awesome-icon icon="angle-right" class="ma4" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex-row grow right center">
|
||||
<DropDown
|
||||
v-if="$store.getters['user/isAdministrator']"
|
||||
class="hideOnMobile"
|
||||
>
|
||||
<button class="flat pa8-left pa8-right">
|
||||
<awesome-icon icon="ellipsis-v" />
|
||||
</button>
|
||||
<template v-slot:dropdown-content>
|
||||
<div>
|
||||
<button @click="uploadNewCover">
|
||||
<awesome-icon icon="image" />Set new Cover...
|
||||
</button>
|
||||
<button @click="resetCover">
|
||||
<awesome-icon icon="eraser" />Reset Cover
|
||||
</button>
|
||||
<hr />
|
||||
<button @click="mergeArtist">
|
||||
<awesome-icon icon="compress-alt" />Merge Artists...
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</DropDown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-row overflow-y">
|
||||
<div id="albumList" class="flex-column hideOnMobile">
|
||||
<AlbumItem
|
||||
class="ma-top ma-left ma-right"
|
||||
:class="{ playing: playingAlbumId == album._id }"
|
||||
v-for="album in selectedArtist.albums"
|
||||
:key="album._id"
|
||||
:item="album"
|
||||
:id="album._id"
|
||||
:ref="album._id"
|
||||
@click="scrollToAlbum(album)"
|
||||
@dblclick="playAlbum(album)"
|
||||
/>
|
||||
</div>
|
||||
<ul
|
||||
id="trackList"
|
||||
class="tracks"
|
||||
:class="{ playing: selectedTrack._id != null }"
|
||||
>
|
||||
<li v-for="track in selectedArtist.tracks" :key="track._id">
|
||||
<TrackItem :track="track" :ref="track._id" />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<ArtistMerge ref="mergeDialog" />
|
||||
</DialogBase>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ArtistMerge from "./ArtistMerge";
|
||||
import TrackItem from "../Track";
|
||||
import { mapGetters } from "vuex";
|
||||
|
||||
export default {
|
||||
mounted() {
|
||||
if (this.selectedArtist._id) {
|
||||
this.$refs.dialogWindow.open();
|
||||
window.addEventListener("keydown", this.keydownListener);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
dblclick() {
|
||||
this.$store.commit("tracks/resetSelectedTrack");
|
||||
this.$store.commit("radios/resetSelectedRadio");
|
||||
this.$store.dispatch("tracks/playContainer", this.selectedArtist);
|
||||
},
|
||||
gotoTrack() {
|
||||
if (this.$route.query.play) {
|
||||
if (!this.selectedTrack._id) {
|
||||
let track = this.selectedArtist.tracks.find(
|
||||
(f) => f._id == this.$route.query.play
|
||||
);
|
||||
if (track) {
|
||||
this.$store.dispatch("tracks/play", track);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
gotoNextArtist() {
|
||||
this.$store.dispatch("artists/gotoNextArtist");
|
||||
},
|
||||
gotoPrevArtist() {
|
||||
this.$store.dispatch("artists/gotoPrevArtist");
|
||||
},
|
||||
closed() {
|
||||
if (
|
||||
window.history.state.back.indexOf("?") == -1 ||
|
||||
window.history.state.back.startsWith("/search")
|
||||
) {
|
||||
this.$router.back();
|
||||
} else {
|
||||
this.$store.dispatch("artists/resetSelectedArtist");
|
||||
}
|
||||
},
|
||||
playAlbum(album) {
|
||||
this.$store.dispatch("tracks/playContainer", album);
|
||||
},
|
||||
keydownListener(e) {
|
||||
if (e.key == "ArrowLeft") {
|
||||
e.preventDefault();
|
||||
this.gotoPrevArtist();
|
||||
}
|
||||
if (e.key == "ArrowRight") {
|
||||
e.preventDefault();
|
||||
this.gotoNextArtist();
|
||||
}
|
||||
},
|
||||
mergeArtist() {
|
||||
this.$refs.mergeDialog.open(this.selectedArtist);
|
||||
},
|
||||
resetCover() {
|
||||
this.$store.dispatch("artists/resetCover", this.selectedArtist);
|
||||
},
|
||||
scrollToAlbum(album) {
|
||||
let track = album.tracks[0];
|
||||
let control = this.$refs[track._id];
|
||||
control[0].scrollFunction(true);
|
||||
control = this.$refs[album._id];
|
||||
control[0].scrollFunction();
|
||||
},
|
||||
toggleFavourite() {
|
||||
this.$store.dispatch("user/toggleFavourite", {
|
||||
itemId: this.selectedArtist._id,
|
||||
type: "artist",
|
||||
});
|
||||
},
|
||||
uploadNewCover() {
|
||||
this.$store.dispatch("artists/uploadNewCover", this.selectedArtist);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
prevArtist: ["artists/prevArtist"],
|
||||
nextArtist: ["artists/nextArtist"],
|
||||
selectedArtist: ["artists/selectedArtist"],
|
||||
selectedTrack: ["tracks/selectedTrack"],
|
||||
favourites: ["user/favourites"],
|
||||
}),
|
||||
cover() {
|
||||
let covers = this.selectedArtist.covers;
|
||||
if (covers.cover512) {
|
||||
return covers.cover512;
|
||||
}
|
||||
return "/static/icons/dummy/artist.svg";
|
||||
},
|
||||
coverBackground() {
|
||||
return "background-image: url('" + this.cover + "')";
|
||||
},
|
||||
artist_albums() {
|
||||
return this.selectedArtist.albums || [];
|
||||
},
|
||||
artist_duration() {
|
||||
let duration = 0;
|
||||
let hours = 0;
|
||||
let minutes = 0;
|
||||
let seconds = 0;
|
||||
|
||||
this.selectedArtist.tracks.forEach((track) => {
|
||||
duration += track.duration;
|
||||
});
|
||||
|
||||
if (duration >= 3600) {
|
||||
hours = parseInt(duration / 3600);
|
||||
duration -= hours * 3600;
|
||||
}
|
||||
|
||||
minutes = parseInt(duration / 60);
|
||||
seconds = parseInt(duration - minutes * 60);
|
||||
return (
|
||||
(hours > 0 ? hours + ":" : "") +
|
||||
(minutes < 10 ? "0" : "") +
|
||||
minutes +
|
||||
":" +
|
||||
(seconds < 10 ? "0" : "") +
|
||||
seconds
|
||||
);
|
||||
},
|
||||
artist_tracks() {
|
||||
return this.selectedArtist.tracks || [];
|
||||
},
|
||||
isFavourite() {
|
||||
return (
|
||||
this.favourites.find((f) => f.itemId == this.selectedArtist._id) !=
|
||||
undefined
|
||||
);
|
||||
},
|
||||
playingAlbumId() {
|
||||
if (this.selectedTrack) {
|
||||
return this.selectedTrack.parent._id;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
selectedArtist(newVal) {
|
||||
if (newVal._id) {
|
||||
if (!this.$refs.dialogWindow.visible) {
|
||||
this.$refs.dialogWindow.open();
|
||||
window.addEventListener("keydown", this.keydownListener);
|
||||
}
|
||||
this.gotoTrack();
|
||||
} else {
|
||||
if (this.$refs.dialogWindow.visible) {
|
||||
this.$refs.dialogWindow.close();
|
||||
}
|
||||
window.removeEventListener("keydown", this.keydownListener);
|
||||
}
|
||||
},
|
||||
},
|
||||
components: {
|
||||
ArtistMerge,
|
||||
TrackItem,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#artistViewer {
|
||||
height: 640px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
h1,
|
||||
#stats {
|
||||
z-index: 1;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
color: var(--white);
|
||||
text-shadow: 0 1px 2px black;
|
||||
}
|
||||
#artistImage {
|
||||
width: 512px;
|
||||
max-height: 256px;
|
||||
}
|
||||
#header {
|
||||
position: relative;
|
||||
background-color: black;
|
||||
width: 760px;
|
||||
max-width: 100%;
|
||||
}
|
||||
#albumList {
|
||||
z-index: 1;
|
||||
overflow-y: auto;
|
||||
background-color: var(--white);
|
||||
}
|
||||
#albumList::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
#albumList .album:last-child {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
#navigation {
|
||||
z-index: 2;
|
||||
background-color: #ffffff40;
|
||||
border-top: 1px solid #ffffff20;
|
||||
border-bottom: 1px solid #00000020;
|
||||
}
|
||||
#trackList {
|
||||
z-index: 1;
|
||||
background-color: var(--white);
|
||||
}
|
||||
|
||||
.album.playing {
|
||||
box-shadow: 0px 6px 12px #000000a0 !important;
|
||||
}
|
||||
|
||||
.dialog-window.fullscreen #artistViewer,
|
||||
.dialog-window.fullscreen #header {
|
||||
width: initial;
|
||||
height: initial;
|
||||
}
|
||||
.dialog-body button {
|
||||
color: var(--darkgray);
|
||||
}
|
||||
.container {
|
||||
flex-grow: 0;
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
}
|
||||
|
||||
@media (max-width: 480px), (max-height: 480px) {
|
||||
#artistViewer {
|
||||
height: initial;
|
||||
}
|
||||
#trackList {
|
||||
width: initial;
|
||||
height: initial;
|
||||
}
|
||||
#header {
|
||||
width: initial;
|
||||
}
|
||||
#albumList {
|
||||
background-color: initial;
|
||||
align-self: center;
|
||||
padding-bottom: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 480px) {
|
||||
}
|
||||
</style>
|
262
src/components/dialogs/AudioUpload.vue
Normal file
@ -0,0 +1,262 @@
|
||||
<template>
|
||||
<DialogBase
|
||||
ref="dialogWindow"
|
||||
:closeOnButtonClick="false"
|
||||
:closeOnFocusLost="false"
|
||||
:maxSize="true"
|
||||
:enableFooterButtons="uploadable"
|
||||
title="Upload audio files..."
|
||||
buttonText="Upload"
|
||||
buttonClass="success large"
|
||||
@accept="uploadFiles(0)"
|
||||
@closing="closing"
|
||||
>
|
||||
<div class="flex-column grow">
|
||||
<input
|
||||
style="display: none"
|
||||
ref="files"
|
||||
type="file"
|
||||
accept="audio/*"
|
||||
@change="openFiles"
|
||||
multiple
|
||||
/>
|
||||
<ul id="audioUploadFiles">
|
||||
<li v-for="(file, i) in files" :key="i" class="pa-top">
|
||||
<h4 class="ma-left darkgray-text">{{ file.name }}</h4>
|
||||
<div class="flex-row ma">
|
||||
<awesome-icon
|
||||
:icon="file.state == 'uploading' ? 'upload' : 'check'"
|
||||
size="2x"
|
||||
:class="{
|
||||
'success-text': file.state == 'done',
|
||||
'gray-text': file.state == 'ready',
|
||||
'primary-text': file.state == 'uploading',
|
||||
}"
|
||||
/>
|
||||
<input
|
||||
placeholder="artist name"
|
||||
type="text"
|
||||
v-model="file.tags.artist"
|
||||
class="left pa8"
|
||||
/>
|
||||
<input
|
||||
placeholder="album name"
|
||||
type="text"
|
||||
v-model="file.tags.album"
|
||||
class="pa8"
|
||||
/>
|
||||
<input
|
||||
placeholder="track number"
|
||||
title="track number"
|
||||
type="text"
|
||||
v-model="file.tags.track"
|
||||
style="width: 32px"
|
||||
class="right pa8"
|
||||
/>
|
||||
<input
|
||||
placeholder="track title"
|
||||
type="text"
|
||||
v-model="file.tags.title"
|
||||
class="pa8 grow"
|
||||
/>
|
||||
<input
|
||||
placeholder="genre"
|
||||
type="text"
|
||||
v-model="file.tags.genre"
|
||||
class="pa8"
|
||||
/>
|
||||
<input
|
||||
placeholder="year"
|
||||
type="text"
|
||||
v-model="file.tags.year"
|
||||
style="width: 48px"
|
||||
class="right pa8"
|
||||
/>
|
||||
<button
|
||||
class="danger"
|
||||
title="Remove from list"
|
||||
@click="remove(file)"
|
||||
>
|
||||
<awesome-icon icon="minus" />
|
||||
</button>
|
||||
</div>
|
||||
<hr />
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div
|
||||
class="flex-column grow center ma primary-text"
|
||||
id="dropzone"
|
||||
@dragover="dropover"
|
||||
@drop="droped"
|
||||
@click="emitFileClick"
|
||||
>
|
||||
<h2>
|
||||
Drop your files here<br />
|
||||
<awesome-icon icon="plus" size="4x" />
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</DialogBase>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
import mmreader from "mp3tag.js";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
files: [],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
this.$refs.dialogWindow.open();
|
||||
},
|
||||
closing() {
|
||||
this.files = [];
|
||||
},
|
||||
dropover(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
},
|
||||
droped(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
let files = [];
|
||||
for (let i = 0; i < e.dataTransfer.files.length; i++) {
|
||||
let file = e.dataTransfer.files[i];
|
||||
if (file.type.indexOf("audio/") == 0) {
|
||||
files.push({ file: file, state: "ready" });
|
||||
}
|
||||
}
|
||||
if (files.length > 0) {
|
||||
this.readTags(files, 0);
|
||||
}
|
||||
},
|
||||
emitFileClick() {
|
||||
this.$refs.files.click();
|
||||
},
|
||||
openFiles(e) {
|
||||
let files = [];
|
||||
if (e.srcElement.value) {
|
||||
for (let i = 0; i < e.srcElement.files.length; i++) {
|
||||
let file = e.srcElement.files[i];
|
||||
files.push({ file: file, state: "ready" });
|
||||
}
|
||||
this.readTags(files, 0);
|
||||
}
|
||||
},
|
||||
readTags(files, index) {
|
||||
let fileReader = new FileReader();
|
||||
var file = files[index];
|
||||
fileReader.onload = () => {
|
||||
if (!this.files.find((f) => f.file.name == file.file.name)) {
|
||||
const mp3tag = new mmreader(fileReader.result);
|
||||
mp3tag.read();
|
||||
file.tags = {};
|
||||
file.tags.title = mp3tag.tags.title;
|
||||
file.tags.artist = mp3tag.tags.artist;
|
||||
file.tags.album = mp3tag.tags.album;
|
||||
file.tags.track = mp3tag.tags.track;
|
||||
file.tags.year = mp3tag.tags.year;
|
||||
if (!file.tags.title || file.tags.title.trim() == "") {
|
||||
file.tags.title = file.file.name;
|
||||
}
|
||||
this.files.push(file);
|
||||
}
|
||||
if (files.length > index + 1) {
|
||||
this.readTags(files, ++index);
|
||||
} else {
|
||||
this.files.sort((a, b) => {
|
||||
if (
|
||||
a.tags.artist > b.tags.artist ||
|
||||
(a.tags.artist == b.tags.artist && a.tags.album > b.tags.album) ||
|
||||
(a.tags.artist == b.tags.artist &&
|
||||
a.tags.album == b.tags.album &&
|
||||
a.tags.track > b.tags.track) ||
|
||||
(a.tags.artist == b.tags.artist &&
|
||||
a.tags.album == b.tags.album &&
|
||||
a.tags.track == b.tags.track &&
|
||||
a.tags.title > b.tags.title)
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
return -1;
|
||||
});
|
||||
}
|
||||
};
|
||||
fileReader.readAsArrayBuffer(file.file);
|
||||
},
|
||||
remove(file) {
|
||||
this.files.splice(this.files.indexOf(file), 1);
|
||||
},
|
||||
uploadFiles(index = 0) {
|
||||
if (this.files.length > index) {
|
||||
let file = this.files[index];
|
||||
if (file.state == "ready") {
|
||||
file.state = "uploading";
|
||||
let formData = new FormData();
|
||||
|
||||
formData.append("file", file.file);
|
||||
formData.append("track", JSON.stringify(file.tags));
|
||||
|
||||
this.$store
|
||||
.dispatch("tracks/upload", formData)
|
||||
.then(() => {
|
||||
file.state = "done";
|
||||
this.uploadFiles(++index);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
this.$refs.dialogWindow.messageText = err;
|
||||
});
|
||||
} else {
|
||||
this.uploadFiles(++index);
|
||||
}
|
||||
} else {
|
||||
this.$store.dispatch("albums/loadNewest");
|
||||
this.$refs.dialogWindow.close();
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
uploadable() {
|
||||
let has_empty_artist =
|
||||
this.files.find((f) => (f.tags.artist || "").trim() == "") != undefined;
|
||||
let has_empty_album =
|
||||
this.files.find((f) => (f.tags.album || "").trim() == "") != undefined;
|
||||
let has_empty_title =
|
||||
this.files.find((f) => (f.tags.title || "").trim() == "") != undefined;
|
||||
let has_empty_track =
|
||||
this.files.find((f) => (f.tags.track || "").trim() == "") != undefined;
|
||||
return (
|
||||
!has_empty_artist &&
|
||||
!has_empty_album &&
|
||||
!has_empty_title &&
|
||||
!has_empty_track
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#audioUploadFiles input {
|
||||
border: none;
|
||||
font-size: large;
|
||||
}
|
||||
#audioUploadFiles button {
|
||||
height: 32px;
|
||||
min-width: 32px;
|
||||
justify-content: center;
|
||||
align-self: center;
|
||||
border-radius: 16px;
|
||||
}
|
||||
#dropzone {
|
||||
border: 2px dashed var(--primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
113
src/components/dialogs/BoxMerge.vue
Normal file
@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<DialogBase
|
||||
ref="dialogWindow"
|
||||
title="Move all videos..."
|
||||
buttonText="Move"
|
||||
buttonClass="accept"
|
||||
@accept="merge"
|
||||
@closed="closed"
|
||||
:closeOnButtonClick="false"
|
||||
:enableFooterButtons="acceptable"
|
||||
>
|
||||
<div class="flex-row" id="merge-content">
|
||||
<div class="flex-column">
|
||||
<h4 class="ma-left ma-top">From</h4>
|
||||
<BoxItem class="ma" :item="source" />
|
||||
</div>
|
||||
<awesome-icon id="arrow-icon" icon="arrow-right" />
|
||||
<div class="flex-column">
|
||||
<DropDown class="ma-left ma-top">
|
||||
<input
|
||||
type="search"
|
||||
v-model="search"
|
||||
@search="searchTermChanged"
|
||||
placeholder="Into"
|
||||
/>
|
||||
<template v-slot:dropdown-content>
|
||||
<div class="flex-column">
|
||||
<h3 class="ma" v-if="boxes.length == 0">Enter an box title</h3>
|
||||
<BoxItem
|
||||
class="ma8"
|
||||
v-for="box in boxes"
|
||||
:key="box._id"
|
||||
:item="box"
|
||||
@click="select(box)"
|
||||
type="line"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</DropDown>
|
||||
<BoxItem class="ma" :item="target" />
|
||||
</div>
|
||||
</div>
|
||||
</DialogBase>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
boxes: [],
|
||||
search: "",
|
||||
searchTimer: 0,
|
||||
source: {},
|
||||
target: { covers: {} },
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
closed() {
|
||||
this.boxes = [];
|
||||
this.source = {};
|
||||
this.target = { covers: {} };
|
||||
this.search = "";
|
||||
clearTimeout(this.searchTimer);
|
||||
},
|
||||
open(source) {
|
||||
this.source = source;
|
||||
this.$refs.dialogWindow.open();
|
||||
},
|
||||
merge() {
|
||||
this.$store
|
||||
.dispatch("boxes/move", {
|
||||
source: this.source._id,
|
||||
target: this.target._id,
|
||||
})
|
||||
.then(() => {
|
||||
this.$store.dispatch("boxes/loadBox", this.target._id);
|
||||
this.$store.dispatch("boxes/selectBoxById", this.target._id);
|
||||
this.$store.dispatch("boxes/remove", this.source._id);
|
||||
this.$refs.dialogWindow.close();
|
||||
});
|
||||
},
|
||||
searchTermChanged() {
|
||||
clearTimeout(this.searchTimer);
|
||||
this.searchTimer = setTimeout(() => {
|
||||
this.$store.dispatch("boxes/filter", this.search).then((result) => {
|
||||
this.boxes = result;
|
||||
});
|
||||
}, 250);
|
||||
},
|
||||
select(album) {
|
||||
this.target = album;
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
acceptable() {
|
||||
return this.source._id != undefined && this.target._id != undefined;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
input {
|
||||
width: 128px;
|
||||
}
|
||||
#arrow-icon {
|
||||
align-self: center;
|
||||
}
|
||||
#merge-content {
|
||||
align-items: flex-end;
|
||||
}
|
||||
</style>
|
301
src/components/dialogs/BoxViewer.vue
Normal file
@ -0,0 +1,301 @@
|
||||
<template>
|
||||
<DialogBase
|
||||
ref="dialogWindow"
|
||||
:title="selectedBox.title"
|
||||
@canceled="closed"
|
||||
:showFooter="false"
|
||||
:closeOnEscape="selectedVideo._id == null"
|
||||
:disableXscroll="true"
|
||||
:disableYscroll="true"
|
||||
>
|
||||
<div id="boxViewer">
|
||||
<div id="header" class="flex-column">
|
||||
<div id="background" :style="coverBackground" />
|
||||
<awesome-icon
|
||||
icon="star"
|
||||
size="2x"
|
||||
class="favourite ma4 z2"
|
||||
:class="{ active: isFavourite }"
|
||||
@click="toggleFavourite"
|
||||
/>
|
||||
<div id="navigation" class="flex-row grow center z1">
|
||||
<img class="glow ma" :src="cover" @dblclick="dblclick" />
|
||||
</div>
|
||||
<div id="stats" class="z1 pa4">
|
||||
<div class="flex-row">
|
||||
<DropDown v-if="$store.getters['user/isAdministrator']">
|
||||
<button
|
||||
class="flat ma4 pa8-left pa8-right"
|
||||
:title="visibility_text"
|
||||
>
|
||||
<awesome-icon :icon="visibility_icon" />
|
||||
</button>
|
||||
<template v-slot:dropdown-content>
|
||||
<div>
|
||||
<button
|
||||
v-for="(item, i) in $store.state.system.lists.visibility"
|
||||
:key="i"
|
||||
@click="setVisibility(item)"
|
||||
>
|
||||
<awesome-icon :icon="getVisibilityIcon(item)" />{{
|
||||
getVisibilityText(item)
|
||||
}}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</DropDown>
|
||||
<span class="grow center">
|
||||
<b>{{ selectedBox.title }}</b>
|
||||
<br />
|
||||
<b>{{ box_videos.length }}</b> Videos
|
||||
</span>
|
||||
<DropDown v-if="$store.getters['user/isAdministrator']">
|
||||
<button class="flat ma4 pa8-left pa8-right">
|
||||
<awesome-icon icon="ellipsis-v" />
|
||||
</button>
|
||||
<template v-slot:dropdown-content>
|
||||
<div>
|
||||
<button @click="uploadNewCover">
|
||||
<awesome-icon icon="image" />Set new Cover...
|
||||
</button>
|
||||
<button @click="resetCover">
|
||||
<awesome-icon icon="eraser" />Reset Cover
|
||||
</button>
|
||||
<hr />
|
||||
<button @click="mergeBox">
|
||||
<awesome-icon icon="compress-alt" />Merge Boxes...
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</DropDown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul id="videoList" class="videos">
|
||||
<li v-for="item in selectedBox.videos" :key="item._id">
|
||||
<VideoItem :video="item" />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<BoxMerge ref="mergeDialog" />
|
||||
</DialogBase>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BoxMerge from "./BoxMerge";
|
||||
import VideoItem from "../Video";
|
||||
import { mapGetters } from "vuex";
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
dblclick() {
|
||||
this.$store.commit("tracks/resetSelectedTrack");
|
||||
this.$store.commit("radios/resetSelectedRadio");
|
||||
this.$store.dispatch("videos/playContainer", this.selectedBox);
|
||||
},
|
||||
gotoVideo() {
|
||||
if (this.$route.query.play) {
|
||||
let video = this.selectedBox.videos.find(
|
||||
(f) => f._id == this.$route.query.play
|
||||
);
|
||||
if (video) {
|
||||
this.$store.dispatch("videos/play", video);
|
||||
}
|
||||
}
|
||||
},
|
||||
gotoNextBox() {
|
||||
this.$store.dispatch("boxes/gotoNextBox");
|
||||
},
|
||||
gotoPrevBox() {
|
||||
this.$store.dispatch("boxes/gotoPrevBox");
|
||||
},
|
||||
closed() {
|
||||
if (
|
||||
window.history.state.back.indexOf("?") == -1 ||
|
||||
window.history.state.back.startsWith("/search")
|
||||
) {
|
||||
this.$router.back();
|
||||
} else {
|
||||
this.$store.dispatch("boxes/resetSelectedBox");
|
||||
}
|
||||
},
|
||||
keydownListener(e) {
|
||||
if (e.key == "ArrowLeft") {
|
||||
e.preventDefault();
|
||||
this.gotoPrevBox();
|
||||
}
|
||||
if (e.key == "ArrowRight") {
|
||||
e.preventDefault();
|
||||
this.gotoNextBox();
|
||||
}
|
||||
},
|
||||
mergeBox() {
|
||||
this.$refs.mergeDialog.open(this.selectedBox);
|
||||
},
|
||||
toggleFavourite() {
|
||||
this.$store.dispatch("user/toggleFavourite", {
|
||||
itemId: this.selectedBox._id,
|
||||
type: "box",
|
||||
});
|
||||
},
|
||||
setVisibility(visibility) {
|
||||
this.selectedBox.visibility = visibility;
|
||||
this.$store.dispatch("boxes/updateBox", this.selectedBox);
|
||||
},
|
||||
uploadNewCover() {
|
||||
this.$store.dispatch("boxes/uploadNewCover", this.selectedBox);
|
||||
},
|
||||
getVisibilityIcon(visibility) {
|
||||
return visibility == "global"
|
||||
? "globe"
|
||||
: visibility == "instance"
|
||||
? "server"
|
||||
: visibility == "hidden"
|
||||
? "eye-slash"
|
||||
: "user";
|
||||
},
|
||||
getVisibilityText(visibility) {
|
||||
return visibility == "global"
|
||||
? "Global"
|
||||
: visibility == "instance"
|
||||
? "On this server"
|
||||
: visibility == "hidden"
|
||||
? "Hide this Box"
|
||||
: "Only for me";
|
||||
},
|
||||
resetCover() {
|
||||
this.$store.dispatch("boxes/resetCover", this.selectedBox);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
prevBox: ["boxes/prevBox"],
|
||||
nextBox: ["boxes/nextBox"],
|
||||
selectedBox: ["boxes/selectedBox"],
|
||||
selectedVideo: ["videos/selectedVideo"],
|
||||
favourites: ["user/favourites"],
|
||||
}),
|
||||
box_videos() {
|
||||
return this.selectedBox.videos || [];
|
||||
},
|
||||
box_year() {
|
||||
return this.selectedBox.year;
|
||||
},
|
||||
coverBackground() {
|
||||
return "background-image: url('" + this.cover + "')";
|
||||
},
|
||||
cover() {
|
||||
let cover = "/static/icons/dummy/box.svg";
|
||||
if (this.selectedBox.covers && this.selectedBox.covers.cover256) {
|
||||
cover = this.selectedBox.covers.cover256;
|
||||
}
|
||||
return cover;
|
||||
},
|
||||
isFavourite() {
|
||||
return (
|
||||
this.favourites.find((f) => f.itemId == this.selectedBox._id) !=
|
||||
undefined
|
||||
);
|
||||
},
|
||||
visibility_icon() {
|
||||
return this.selectedBox.visibility == "global"
|
||||
? "globe"
|
||||
: this.selectedBox.visibility == "instance"
|
||||
? "server"
|
||||
: this.selectedBox.visibility == "hidden"
|
||||
? "eye-slash"
|
||||
: "user";
|
||||
},
|
||||
visibility_text() {
|
||||
return this.selectedBox.visibility == "global"
|
||||
? "Visible for the whole world"
|
||||
: this.selectedBox.visibility == "instance"
|
||||
? "Visible on this instance"
|
||||
: this.selectedBox.visibility == "hidden"
|
||||
? "Hidden for all users"
|
||||
: "Visible only for me";
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
selectedBox(newVal) {
|
||||
if (newVal._id) {
|
||||
if (!this.$refs.dialogWindow.visible) {
|
||||
this.$refs.dialogWindow.open();
|
||||
window.addEventListener("keydown", this.keydownListener);
|
||||
}
|
||||
this.gotoVideo();
|
||||
} else {
|
||||
if (this.$refs.dialogWindow.visible) {
|
||||
this.$refs.dialogWindow.close();
|
||||
}
|
||||
window.removeEventListener("keydown", this.keydownListener);
|
||||
}
|
||||
},
|
||||
},
|
||||
components: {
|
||||
BoxMerge,
|
||||
VideoItem,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#boxViewer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
#header {
|
||||
position: relative;
|
||||
min-width: 280px;
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
#videoList {
|
||||
height: 440px;
|
||||
background-color: var(--white);
|
||||
z-index: 1;
|
||||
overflow: overlay;
|
||||
}
|
||||
|
||||
#stats {
|
||||
z-index: 2;
|
||||
color: var(--white);
|
||||
text-shadow: 0 1px 2px black;
|
||||
line-height: 1.4;
|
||||
background-color: #ffffff40;
|
||||
border-top: 1px solid #ffffff20;
|
||||
border-bottom: 1px solid #00000020;
|
||||
}
|
||||
|
||||
.video {
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
#boxViewer {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px), (max-height: 480px) {
|
||||
#videoList {
|
||||
max-height: initial;
|
||||
height: initial;
|
||||
}
|
||||
.video {
|
||||
width: initial;
|
||||
}
|
||||
|
||||
#navigation img{
|
||||
height: 220px;
|
||||
}
|
||||
}
|
||||
@media (max-height: 480px) {
|
||||
#navigation {
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
45
src/components/dialogs/DesktopSettings.vue
Normal file
@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<DialogBase ref="dialogWindow" title="Settings" @closed="closed">
|
||||
<div id="settingsBody">
|
||||
<table class="configValues">
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<h4>Configuration</h4>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Backend</td>
|
||||
<td>
|
||||
<input v-model.lazy="backend" @change="backendChanged" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</DialogBase>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: "SettingsDialog",
|
||||
data() {
|
||||
return {
|
||||
backend: "",
|
||||
newBackend: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
this.$refs.dialogWindow.open();
|
||||
this.backend = this.$store.state.server;
|
||||
},
|
||||
backendChanged() {
|
||||
this.$store.dispatch("setNewBackend", this.backend);
|
||||
this.newBackend = true;
|
||||
},
|
||||
closed() {
|
||||
if (this.newBackend) {
|
||||
window.location.href = "/";
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
121
src/components/dialogs/Radios.vue
Normal file
@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<DialogBase
|
||||
ref="dialogWindow"
|
||||
title="Radio Stations"
|
||||
:showCloseButton="false"
|
||||
@closed="closed"
|
||||
buttonText="close"
|
||||
>
|
||||
<template v-slot:header-right>
|
||||
<div id="newRadio">
|
||||
<input
|
||||
id="radioName"
|
||||
ref="newName"
|
||||
v-model="newName"
|
||||
type="text"
|
||||
placeholder="Station Name"
|
||||
required
|
||||
pattern=".+"
|
||||
/>
|
||||
<input
|
||||
id="radioUrl"
|
||||
ref="newUrl"
|
||||
v-model="newUrl"
|
||||
type="text"
|
||||
placeholder="Add a stream url"
|
||||
required
|
||||
pattern="https?://.+"
|
||||
@keydown.enter="addRadio"
|
||||
/>
|
||||
<button @click="addRadio" title="Add new Radio Station" class="success">
|
||||
<awesome-icon icon="plus" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<div
|
||||
id="radiosBody"
|
||||
class="ma4-top ma4-left ma4-bottom"
|
||||
v-if="$store.getters['radios/collection'].length > 0"
|
||||
>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2">Name</th>
|
||||
<th class="maxWidth">Url</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(radio, i) in $store.getters['radios/collection']"
|
||||
:key="i"
|
||||
>
|
||||
<td>
|
||||
<img
|
||||
class="radioCover"
|
||||
:src="radio.cover32"
|
||||
@click="updateCover(radio)"
|
||||
/>
|
||||
</td>
|
||||
<td>{{ radio.name }}</td>
|
||||
<td>{{ radio.url }}</td>
|
||||
<td>
|
||||
<button
|
||||
@click="deleteRadio(radio)"
|
||||
title="Remove Radio Station"
|
||||
class="flat"
|
||||
>
|
||||
<awesome-icon icon="trash-alt" />
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</DialogBase>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: "RadiosDialog",
|
||||
data() {
|
||||
return {
|
||||
newName: "",
|
||||
newUrl: "",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
this.$store.state.systemDialog = true;
|
||||
this.$refs.dialogWindow.open();
|
||||
this.$nextTick(() => {
|
||||
this.$refs.newName.focus();
|
||||
});
|
||||
},
|
||||
closed() {
|
||||
this.$store.state.systemDialog = false;
|
||||
},
|
||||
isInputValid() {
|
||||
let inputs = document.querySelectorAll("#newRadio input");
|
||||
for (let i = 0; i < inputs.length; i++) {
|
||||
if (!inputs[i].validity.valid) {
|
||||
inputs[i].focus();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
addRadio() {
|
||||
if (this.isInputValid()) {
|
||||
let newRadio = { name: this.newName, url: this.newUrl };
|
||||
this.$store.dispatch("radios/addRadio", newRadio);
|
||||
}
|
||||
},
|
||||
deleteRadio(radio) {
|
||||
this.$store.dispatch("radios/deleteRadio", radio);
|
||||
},
|
||||
updateCover(radio) {
|
||||
this.$store.dispatch("radios/updateRadio", radio);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
119
src/components/dialogs/ServerSettings.vue
Normal file
@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<DialogBase
|
||||
ref="dialogWindow"
|
||||
title="Server Settings"
|
||||
@accept="accept"
|
||||
@closing="closing"
|
||||
:closeOnButtonClick="false"
|
||||
:closeOnFocusLost="false"
|
||||
buttonText="Accept"
|
||||
buttonClass="accept"
|
||||
>
|
||||
<h4 class="ma-top">General</h4>
|
||||
<hr class="ma-right ma-left" />
|
||||
<ul class="ma">
|
||||
<li>
|
||||
<input type="checkbox" id="allowGuests" v-model="allows.guests" />
|
||||
<label for="allowGuests">Allow Guests</label>
|
||||
</li>
|
||||
<li>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="allowRegistrations"
|
||||
v-model="allows.register"
|
||||
/>
|
||||
<label for="allowRegistrations">Allow Registration</label>
|
||||
</li>
|
||||
</ul>
|
||||
<h4 class="ma-top">Actions</h4>
|
||||
<hr class="ma-right ma-left" />
|
||||
<div><button class="danger ma" @click="resetRedisCache">Reset Redis Cache</button></div>
|
||||
|
||||
<h4 class="ma-top">Accepted Domains</h4>
|
||||
<hr class="ma-right ma-left" />
|
||||
<ul class="ma-top ma-left">
|
||||
<li v-for="(domain, i) in domains.const" :key="i">
|
||||
<span> {{ domain }}</span>
|
||||
</li>
|
||||
<li v-for="(domain, i) in domains.dynamic" :key="i">
|
||||
<span> {{ domain }}</span
|
||||
><button class="flat danger" @click="removeDomain(domain)">
|
||||
<awesome-icon icon="trash-alt" />
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<input
|
||||
type="url"
|
||||
class="ma"
|
||||
v-model="newDomain"
|
||||
placeholder="Add new domain"
|
||||
@change="addDomain"
|
||||
/>
|
||||
</DialogBase>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
allows: { guests: false, register: false },
|
||||
domains: {},
|
||||
newDomain: "",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
accept() {
|
||||
this.$store.dispatch("saveSystemAllows", this.allows);
|
||||
this.$store.dispatch("saveSystemDomains", this.domains);
|
||||
this.$refs.dialogWindow.close();
|
||||
},
|
||||
addDomain(e) {
|
||||
if (e.srcElement.checkValidity()) {
|
||||
this.domains.dynamic.push(this.newDomain);
|
||||
this.newDomain = "";
|
||||
}
|
||||
},
|
||||
closing() {},
|
||||
open() {
|
||||
this.allows = { ...this.$store.getters.serverConfig.allows };
|
||||
this.$store.dispatch("loadSystemDomains").then((domains) => {
|
||||
this.domains = { ...domains };
|
||||
});
|
||||
this.$refs.dialogWindow.open();
|
||||
},
|
||||
removeDomain(domain) {
|
||||
this.domains.dynamic.splice(this.domains.dynamic.indexOf(domain), 1);
|
||||
},
|
||||
resetRedisCache(){
|
||||
this.$store.dispatch("resetRedisCache");
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
h4 {
|
||||
margin-left: 12px;
|
||||
}
|
||||
li {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
li:not(:last-child) {
|
||||
display: flex;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
li input {
|
||||
margin-right: 4px;
|
||||
}
|
||||
li span {
|
||||
flex-grow: 1;
|
||||
}
|
||||
li button {
|
||||
opacity: 0.25;
|
||||
}
|
||||
li button:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
input[type="url"] {
|
||||
margin-right: 24px;
|
||||
}
|
||||
</style>
|
167
src/components/dialogs/UserProfile.vue
Normal file
@ -0,0 +1,167 @@
|
||||
<template>
|
||||
<DialogBase
|
||||
ref="dialogWindow"
|
||||
:title="$store.state.user.name + '\'s Profile'"
|
||||
@closed="closed"
|
||||
:showCloseButton="false"
|
||||
:closeOnFocusLost="false"
|
||||
:closeOnEscape="false"
|
||||
>
|
||||
<div id="profileBody" class="ma">
|
||||
<table class="padding">
|
||||
<tr>
|
||||
<td colspan="2"><h4>Set new password</h4></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div id="newUserPass">
|
||||
<input
|
||||
autocomplete="off"
|
||||
type="password"
|
||||
ref="oldPass"
|
||||
v-model="oldPass"
|
||||
placeholder="Current Password"
|
||||
/>
|
||||
|
||||
<input
|
||||
autocomplete="off"
|
||||
type="password"
|
||||
ref="newPass"
|
||||
v-model="newPass"
|
||||
placeholder="New Password"
|
||||
@keydown.enter="changePassword"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
<tr>
|
||||
<td colspan="2"><h4 class="ma-top">Configuration</h4></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Clear history</td>
|
||||
<td>
|
||||
<button @click="clearHistory">clear</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><h4 class="ma-top">Audioquality</h4></td>
|
||||
<td colspan="2"><h4 class="ma-top">Video</h4></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>On Moble Devices</td>
|
||||
<td>
|
||||
<select v-model="$store.getters['user/settings'].mobile_bpm">
|
||||
<option
|
||||
v-for="(item, i) in this.$store.state.system.lists
|
||||
.audio_quality"
|
||||
:key="i"
|
||||
>
|
||||
{{ item }}
|
||||
</option>
|
||||
</select>
|
||||
<span>kBit/s</span>
|
||||
</td>
|
||||
<td>Preferred Language</td>
|
||||
<td class="fillCell">
|
||||
<select v-model="$store.getters['user/settings'].video_lang">
|
||||
<option
|
||||
v-for="(item, i) in this.$store.state.system.lists.video_lang"
|
||||
:key="i"
|
||||
>
|
||||
{{ item }}
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>On Desktop Devices</td>
|
||||
<td>
|
||||
<select v-model="$store.getters['user/settings'].desktop_bpm">
|
||||
<option
|
||||
v-for="(item, i) in this.$store.state.system.lists
|
||||
.audio_quality"
|
||||
:key="i"
|
||||
>
|
||||
{{ item }}
|
||||
</option>
|
||||
</select>
|
||||
<span>kBit/s</span>
|
||||
</td>
|
||||
<td>Quality</td>
|
||||
<td class="fillCell">
|
||||
<select v-model="$store.getters['user/settings'].video_quality">
|
||||
<option
|
||||
v-for="(item, i) in this.$store.state.system.lists
|
||||
.video_quality"
|
||||
:key="i"
|
||||
>
|
||||
{{ item }}
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</DialogBase>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loaded: false,
|
||||
oldPass: "",
|
||||
newPass: "",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
if (!this.loaded) {
|
||||
this.$store.dispatch("system/loadLists");
|
||||
this.$store.state.systemDialog = true;
|
||||
this.loaded = true;
|
||||
}
|
||||
this.$refs.dialogWindow.open();
|
||||
this.$nextTick(() => {
|
||||
this.$refs.oldPass.focus();
|
||||
});
|
||||
},
|
||||
closed() {
|
||||
this.$store.dispatch("user/updateConfig");
|
||||
this.$store.state.systemDialog = false;
|
||||
},
|
||||
changePassword() {
|
||||
console.log("changePassword");
|
||||
let user = { oldPass: this.oldPass, newPass: this.newPass };
|
||||
this.$store
|
||||
.dispatch("user/update", user)
|
||||
.then((response) => {
|
||||
switch (response.status) {
|
||||
case 202:
|
||||
this.oldPass = "";
|
||||
this.newPass = "";
|
||||
this.$refs.dialogWindow.messageText = "Password changed";
|
||||
this.$refs.dialogWindow.focusButton();
|
||||
break;
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
switch (e.status) {
|
||||
case 401:
|
||||
this.$refs.dialogWindow.messageText = "Not Authorized";
|
||||
break;
|
||||
case 422:
|
||||
this.$refs.oldPass.focus();
|
||||
this.$refs.oldPass.select();
|
||||
this.$refs.dialogWindow.messageText = "Password is incorrect";
|
||||
break;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
clearHistory() {
|
||||
this.$store.dispatch("user/cleanHistory");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
214
src/components/dialogs/Users.vue
Normal file
@ -0,0 +1,214 @@
|
||||
<template>
|
||||
<DialogBase
|
||||
ref="dialogWindow"
|
||||
title="Users"
|
||||
:showCloseButton="false"
|
||||
buttonText="Close"
|
||||
@closed="closed"
|
||||
@opened="loadContent"
|
||||
>
|
||||
<template v-slot:header-right>
|
||||
<div id="newUser">
|
||||
<input
|
||||
placeholder="New User"
|
||||
v-model="newUser"
|
||||
ref="newUser"
|
||||
type="text"
|
||||
required
|
||||
pattern=".+"
|
||||
autocomplete="one-time-code"
|
||||
/>
|
||||
<input
|
||||
placeholder="Password"
|
||||
v-model="newPass"
|
||||
ref="newPass"
|
||||
type="password"
|
||||
@keydown.enter="validateAndSaveInput"
|
||||
required
|
||||
pattern=".{5,}"
|
||||
/>
|
||||
<button
|
||||
@click="validateAndSaveInput"
|
||||
title="Add new Useraccount"
|
||||
class="success"
|
||||
>
|
||||
<awesome-icon icon="user-plus" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<div id="usersBody" class="ma-top ma-left ma-bottom">
|
||||
<table class="padding">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th v-if="$store.getters['user/isAdministrator']">Roles</th>
|
||||
<th>Last Access</th>
|
||||
<th>Change Password</th>
|
||||
<th class="slim" v-if="$store.getters['user/isAdministrator']"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(user, i) in users" :key="i">
|
||||
<td :class="{ me: user._id == me._id }">
|
||||
{{ user.name }}
|
||||
</td>
|
||||
<td v-if="$store.getters['user/isAdministrator']">
|
||||
<DropDown v-if="user._id != me._id" :closeOnClick="false">
|
||||
<button class="flat">
|
||||
<awesome-icon icon="user-cog" />
|
||||
</button>
|
||||
<template v-slot:dropdown-content>
|
||||
<div
|
||||
class="user-role ma4"
|
||||
v-for="(role, i) in lists.user_role"
|
||||
:key="i"
|
||||
>
|
||||
<input
|
||||
:id="role"
|
||||
class="ma4-right"
|
||||
type="checkbox"
|
||||
:checked="user.roles && user.roles.includes(role)"
|
||||
@change="changeRole(user, role)"
|
||||
/>
|
||||
<label :for="role">{{ role }}</label>
|
||||
</div>
|
||||
</template>
|
||||
</DropDown>
|
||||
</td>
|
||||
<td class="right">{{ user.last_access }}</td>
|
||||
<td>
|
||||
<input
|
||||
type="password"
|
||||
placeholder="New Password"
|
||||
@keydown.enter="updateUser(user, $event)"
|
||||
/>
|
||||
</td>
|
||||
<td v-if="$store.getters['user/isAdministrator']" class="slim">
|
||||
<button
|
||||
@click="deleteUser(user)"
|
||||
title="Remove Useraccount"
|
||||
class="flat danger"
|
||||
v-if="user._id != me._id"
|
||||
>
|
||||
<awesome-icon icon="trash-alt" />
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</DialogBase>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
|
||||
export default {
|
||||
name: "UsersDialog",
|
||||
data() {
|
||||
return {
|
||||
newUser: "",
|
||||
newPass: "",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
this.$store.state.systemDialog = true;
|
||||
this.$refs.dialogWindow.open();
|
||||
this.$nextTick(() => {
|
||||
this.$refs.newUser.focus();
|
||||
});
|
||||
},
|
||||
closed() {
|
||||
this.$store.state.systemDialog = false;
|
||||
},
|
||||
loadContent() {
|
||||
this.$store.dispatch("system/loadUsers");
|
||||
},
|
||||
updateUser(user, event) {
|
||||
var newUserPassword = event.srcElement.value;
|
||||
if (newUserPassword.length == 0) {
|
||||
console.log("No Password");
|
||||
return;
|
||||
}
|
||||
user.newPassword = newUserPassword;
|
||||
this.$store.dispatch("system/updateUser", user).then(() => {
|
||||
this.$refs.dialogWindow.messageText =
|
||||
"Password changed for " + user.name;
|
||||
this.$refs.dialogWindow.focusButton();
|
||||
});
|
||||
},
|
||||
deleteUser(user) {
|
||||
this.$store.dispatch("system/deleteUser", user);
|
||||
},
|
||||
validateAndSaveInput() {
|
||||
this.newUser = this.newUser.trim();
|
||||
if (this.newUser.length == 0) {
|
||||
this.$refs.dialogWindow.messageText = "No Username";
|
||||
this.$refs.newUser.focus();
|
||||
return;
|
||||
}
|
||||
if (this.newPass.length == 0) {
|
||||
this.$refs.dialogWindow.messageText = "No Password";
|
||||
this.$refs.newPass.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
let newUser = { name: this.newUser, password: this.newPass };
|
||||
this.$store
|
||||
.dispatch("system/addUserIfNotExists", newUser)
|
||||
.then((success) => {
|
||||
if (!success) {
|
||||
this.$refs.dialogWindow.messageText = "Username bereits vergeben";
|
||||
}
|
||||
});
|
||||
},
|
||||
changeRole(user, role) {
|
||||
let i = user.roles.indexOf(role);
|
||||
if (i > -1) {
|
||||
user.roles.splice(i, 1);
|
||||
} else {
|
||||
user.roles.push(role);
|
||||
}
|
||||
delete user.newPassword;
|
||||
this.$store.dispatch("system/updateUser", user);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
users: ["system/users"],
|
||||
lists: ["system/lists"],
|
||||
}),
|
||||
me() {
|
||||
return this.$store.state.user;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#newUser {
|
||||
display: flex;
|
||||
}
|
||||
#usersBody th {
|
||||
text-align: left;
|
||||
}
|
||||
#usersBody td {
|
||||
color: var(--gray);
|
||||
}
|
||||
#usersBody .me {
|
||||
font-weight: bold;
|
||||
}
|
||||
#usersBody table button {
|
||||
opacity: 0.25;
|
||||
}
|
||||
#usersBody table button:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
#usersBody .user-role {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
td input {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
173
src/components/dialogs/VideoScreen.vue
Normal file
@ -0,0 +1,173 @@
|
||||
<template>
|
||||
<DialogBase
|
||||
ref="dialogWindow"
|
||||
@closing="closing"
|
||||
:showFooter="false"
|
||||
:showFullscreenButton="true"
|
||||
>
|
||||
<template v-slot:header-right>
|
||||
<select
|
||||
v-if="selectedVideo.tracks && selectedVideo.tracks.length > 1"
|
||||
@change="langChanged"
|
||||
v-model="selectedLang"
|
||||
>
|
||||
<option
|
||||
v-for="(lang, i) in selectedVideo.tracks"
|
||||
:key="i"
|
||||
:value="lang"
|
||||
:title="lang.title"
|
||||
>
|
||||
{{ lang.lang.toUpperCase() }}
|
||||
</option>
|
||||
</select>
|
||||
</template>
|
||||
<video
|
||||
@ended="nextVideo"
|
||||
@timeupdate="timeUpdate"
|
||||
controls
|
||||
style="height: 100%; width: 100%; background: black"
|
||||
ref="videoControl"
|
||||
src
|
||||
></video>
|
||||
</DialogBase>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
video: undefined,
|
||||
languages: [],
|
||||
selectedLang: {},
|
||||
preConvert: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
langChanged() {
|
||||
this.video.src =
|
||||
this.$store.getters["videos/getStreamUrl"] + this.langIndex;
|
||||
this.video.play();
|
||||
},
|
||||
playVideo(video) {
|
||||
this.$store.commit("radios/resetSelectedRadio");
|
||||
this.$store.commit("tracks/resetSelectedTrack");
|
||||
this.$refs.dialogWindow.open(video.title);
|
||||
this.preConvert = false;
|
||||
this.languages = [];
|
||||
if (!this.video) {
|
||||
this.$nextTick(() => {
|
||||
this.video = this.$refs.videoControl;
|
||||
this.playStream(video);
|
||||
});
|
||||
} else {
|
||||
this.playStream(video);
|
||||
}
|
||||
},
|
||||
playStream(video) {
|
||||
let lastLang = this.selectedLang.lang;
|
||||
let findLang = video.tracks.find(
|
||||
(f) =>
|
||||
f.lang.toUpperCase() ==
|
||||
(
|
||||
lastLang ||
|
||||
this.$store.getters["user/settings"].video_lang ||
|
||||
"ENG"
|
||||
).toUpperCase()
|
||||
);
|
||||
|
||||
this.selectedLang = findLang || video.tracks[0];
|
||||
|
||||
this.video.src =
|
||||
this.$store.getters["videos/getStreamUrl"] + this.langIndex;
|
||||
this.video.play();
|
||||
|
||||
this.pushHistoryItem();
|
||||
},
|
||||
nextVideo() {
|
||||
this.$store.dispatch("videos/playNextTo", this.selectedVideo);
|
||||
},
|
||||
pushHistoryItem() {
|
||||
let item = {
|
||||
id: this.selectedVideo.parent._id,
|
||||
type: "box",
|
||||
title: this.selectedVideo.parent.title,
|
||||
covers: { cover128: this.selectedVideo.parent.covers.cover128 },
|
||||
};
|
||||
this.$store.dispatch("user/saveHistoryItem", item);
|
||||
this.scaleImage(this.selectedVideo.thumbnail, 0.5, (img) => {
|
||||
item = {
|
||||
id: this.selectedVideo._id,
|
||||
type: "video",
|
||||
title: this.selectedVideo.title,
|
||||
thumbnail: img,
|
||||
parent: { _id: this.selectedVideo.parent._id },
|
||||
};
|
||||
this.$store.dispatch("user/saveHistoryItem", item);
|
||||
});
|
||||
},
|
||||
timeUpdate(event) {
|
||||
let percent = (event.target.currentTime / event.target.duration) * 100;
|
||||
if (percent > 30 && !this.preConvert) {
|
||||
this.preConvert = true;
|
||||
this.$store.dispatch("videos/convertNextTo", {
|
||||
video: this.selectedVideo,
|
||||
langIndex: this.langIndex,
|
||||
});
|
||||
}
|
||||
},
|
||||
closing() {
|
||||
this.video = undefined;
|
||||
this.$store.dispatch("videos/resetSelectedVideo");
|
||||
},
|
||||
scaleImage(src, factor, callback) {
|
||||
let img = document.createElement("img");
|
||||
|
||||
img.onload = () => {
|
||||
var canvas = document.createElement("canvas");
|
||||
var ctx = canvas.getContext("2d");
|
||||
ctx.imageSmoothingEnabled = true;
|
||||
ctx.imageSmoothingQuality = "high";
|
||||
|
||||
canvas.width = img.width * factor;
|
||||
canvas.height = img.height * factor;
|
||||
|
||||
let width = img.width;
|
||||
let height = img.height;
|
||||
|
||||
ctx.drawImage(
|
||||
img,
|
||||
0,
|
||||
0,
|
||||
width,
|
||||
height,
|
||||
0,
|
||||
0,
|
||||
width * factor,
|
||||
height * factor
|
||||
);
|
||||
|
||||
callback(canvas.toDataURL());
|
||||
};
|
||||
img.src = src;
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
selectedVideo() {
|
||||
return this.$store.getters["videos/selectedVideo"];
|
||||
},
|
||||
langIndex() {
|
||||
return this.selectedVideo.tracks.indexOf(this.selectedLang);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
selectedVideo(newVal) {
|
||||
if (newVal._id) {
|
||||
this.playVideo(newVal);
|
||||
} else {
|
||||
if (this.$refs.dialogWindow.visible) {
|
||||
this.$refs.dialogWindow.close();
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
223
src/components/dialogs/VideoUpload.vue
Normal file
@ -0,0 +1,223 @@
|
||||
<template>
|
||||
<DialogBase
|
||||
ref="dialogWindow"
|
||||
:closeOnButtonClick="false"
|
||||
:closeOnFocusLost="false"
|
||||
:maxSize="true"
|
||||
:enableFooterButtons="uploadable"
|
||||
title="Upload video files..."
|
||||
buttonText="Upload"
|
||||
buttonClass="success large"
|
||||
@accept="uploadFiles(0)"
|
||||
@closing="closing"
|
||||
>
|
||||
<div class="flex-row grow">
|
||||
<input
|
||||
style="display: none"
|
||||
ref="files"
|
||||
type="file"
|
||||
accept="video/*"
|
||||
@change="openFiles"
|
||||
multiple
|
||||
/>
|
||||
<div class="flex-column" v-if="files.length > 0">
|
||||
<img
|
||||
class="boxCover shadow"
|
||||
:src="coverPreview || 'static/icons/dummy/box.svg'"
|
||||
@click="chooseCover"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
class="ma"
|
||||
placeholder="box title"
|
||||
v-model="box.title"
|
||||
/>
|
||||
<input type="text" class="ma" placeholder="year" v-model="box.year" />
|
||||
</div>
|
||||
<div class="flex-column grow">
|
||||
<ul id="videoUploadFiles">
|
||||
<li v-for="(file, i) in files" :key="i" class="pa-top">
|
||||
<h4 class="ma-left darkgray-text">{{ file.name }}</h4>
|
||||
<div class="flex-row ma">
|
||||
<awesome-icon
|
||||
:icon="file.state == 'uploading' ? 'upload' : 'check'"
|
||||
size="2x"
|
||||
:class="{
|
||||
'success-text': file.state == 'done',
|
||||
'gray-text': file.state == 'ready',
|
||||
'primary-text': file.state == 'uploading',
|
||||
}"
|
||||
/>
|
||||
<input
|
||||
placeholder="video title"
|
||||
type="text"
|
||||
v-model="file.tags.title"
|
||||
class="grow left pa8"
|
||||
/>
|
||||
<button
|
||||
class="danger"
|
||||
title="Remove from list"
|
||||
@click="remove(file)"
|
||||
>
|
||||
<awesome-icon icon="minus" />
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div
|
||||
class="flex-column grow center ma primary-text"
|
||||
id="dropzone"
|
||||
@dragover="dropover"
|
||||
@drop="droped"
|
||||
@click="emitFileClick"
|
||||
>
|
||||
<h2>
|
||||
Drop your files here<br />
|
||||
<awesome-icon icon="plus" size="4x" />
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DialogBase>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
box: { title: "", year: "" },
|
||||
files: [],
|
||||
coverPreview: "",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
this.$refs.dialogWindow.open();
|
||||
},
|
||||
closing() {
|
||||
this.files = [];
|
||||
},
|
||||
chooseCover() {
|
||||
let me = this;
|
||||
let input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.accept = "image/jpeg, image/png";
|
||||
let reader = new FileReader();
|
||||
input.addEventListener("change", function () {
|
||||
if (input.value) {
|
||||
reader.readAsDataURL(input.files[0]);
|
||||
me.box.cover = input.files[0];
|
||||
}
|
||||
});
|
||||
reader.addEventListener("load", function () {
|
||||
me.coverPreview = this.result;
|
||||
});
|
||||
input.click();
|
||||
},
|
||||
dropover(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
},
|
||||
droped(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
let files = [];
|
||||
for (let i = 0; i < e.dataTransfer.files.length; i++) {
|
||||
let file = e.dataTransfer.files[i];
|
||||
if (file.type.indexOf("video/") == 0) {
|
||||
files.push({ file: file, state: "ready" });
|
||||
}
|
||||
}
|
||||
if (files.length > 0) {
|
||||
this.readFiles(files, 0);
|
||||
}
|
||||
},
|
||||
openFiles(e) {
|
||||
let files = [];
|
||||
if (e.srcElement.value) {
|
||||
for (let i = 0; i < e.srcElement.files.length; i++) {
|
||||
let file = e.srcElement.files[i];
|
||||
files.push({ file: file, state: "ready" });
|
||||
}
|
||||
this.readFiles(files, 0);
|
||||
}
|
||||
},
|
||||
readFiles(files, index) {
|
||||
let fileReader = new FileReader();
|
||||
var file = files[index];
|
||||
fileReader.onload = () => {
|
||||
file.tags = {};
|
||||
file.tags.title = file.file.name;
|
||||
this.files.push(file);
|
||||
if (files.length > index + 1) {
|
||||
this.readFiles(files, ++index);
|
||||
}
|
||||
};
|
||||
fileReader.readAsArrayBuffer(file.file);
|
||||
},
|
||||
emitFileClick() {
|
||||
this.$refs.files.click();
|
||||
},
|
||||
remove(file) {
|
||||
this.files.splice(this.files.indexOf(file), 1);
|
||||
},
|
||||
uploadFiles(index = 0) {
|
||||
if (this.files.length > index) {
|
||||
let file = this.files[index];
|
||||
if (file.state == "ready") {
|
||||
file.state = "uploading";
|
||||
let formData = new FormData();
|
||||
formData.append("file", file.file, "file");
|
||||
if (this.box.cover) {
|
||||
formData.append("file", this.box.cover, "cover");
|
||||
delete this.box.cover;
|
||||
}
|
||||
formData.append("title", file.tags.title || "");
|
||||
formData.append("box", JSON.stringify(this.box));
|
||||
this.$store
|
||||
.dispatch("videos/upload", formData)
|
||||
.then(() => {
|
||||
file.state = "done";
|
||||
this.uploadFiles(++index);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
this.$refs.dialogWindow.messageText = err;
|
||||
});
|
||||
} else {
|
||||
this.uploadFiles(++index);
|
||||
}
|
||||
} else {
|
||||
this.$store.dispatch("boxes/loadNewest");
|
||||
this.$refs.dialogWindow.close();
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
uploadable() {
|
||||
return this.files.length > 0 && this.box.title.trim().length > 0;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
img {
|
||||
margin: 12px;
|
||||
width: 256px;
|
||||
height: 362px;
|
||||
}
|
||||
input[type="text"] {
|
||||
border: none;
|
||||
font-size: large;
|
||||
}
|
||||
#videoUploadFiles button {
|
||||
height: 32px;
|
||||
min-width: 32px;
|
||||
justify-content: center;
|
||||
align-self: center;
|
||||
border-radius: 16px;
|
||||
}
|
||||
#dropzone {
|
||||
border: 2px dashed var(--primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
32
src/main.js
Normal file
@ -0,0 +1,32 @@
|
||||
import { createApp } from 'vue'
|
||||
|
||||
import App from './App.vue'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { fas } from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
||||
|
||||
import DialogBase from "./components/base-components/Dialog";
|
||||
import DropDown from "./components/base-components/DropDown";
|
||||
import MessageScreen from "./components/base-components/MessageScreen";
|
||||
|
||||
import AlbumItem from "./components/Album"
|
||||
import ArtistItem from "./components/Artist"
|
||||
import BoxItem from "./components/Box"
|
||||
|
||||
library.add(fas)
|
||||
|
||||
import store from "./store/index";
|
||||
import router from './router'
|
||||
|
||||
const app = createApp(App);
|
||||
app.use(store);
|
||||
app.use(router);
|
||||
app.component('awesome-icon', FontAwesomeIcon);
|
||||
app.component('AlbumItem', AlbumItem);
|
||||
app.component('ArtistItem', ArtistItem);
|
||||
app.component('BoxItem', BoxItem);
|
||||
app.component('DialogBase', DialogBase);
|
||||
app.component('DropDown', DropDown);
|
||||
app.component('MessageScreen', MessageScreen);
|
||||
app.mount('#app');
|
28
src/mixins/BaseCollection.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
item: { covers: {} },
|
||||
},
|
||||
methods: {
|
||||
scrollFunction() {
|
||||
let element = document.getElementById(this.item._id);
|
||||
let bounding = element.getBoundingClientRect();
|
||||
let scrollDown = bounding.top < 56;
|
||||
let scrollUp =
|
||||
bounding.top + bounding.height >
|
||||
(window.innerHeight || document.documentElement.clientHeight);
|
||||
if (scrollDown) {
|
||||
element.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "start",
|
||||
});
|
||||
} else if (scrollUp) {
|
||||
element.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "end",
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
75
src/router.js
Normal file
@ -0,0 +1,75 @@
|
||||
import { createWebHashHistory, createRouter } from "vue-router";
|
||||
|
||||
import LoginView from "./views/Login";
|
||||
import AlbumsView from "./views/Albums";
|
||||
import ArtistsView from "./views/Artists";
|
||||
import RadiosView from "./views/Radios";
|
||||
import BoxesView from "./views/Boxes";
|
||||
import SearchView from "./views/Search";
|
||||
import SetupView from "./views/Setup";
|
||||
import HomeView from "./views/Home";
|
||||
import UsersView from "./views/Users";
|
||||
import FavouritesView from "./views/Favourites"
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: "/",
|
||||
component: HomeView
|
||||
},
|
||||
{
|
||||
path: "/login",
|
||||
component: LoginView
|
||||
},
|
||||
{
|
||||
path: "/albums",
|
||||
component: AlbumsView
|
||||
},
|
||||
{
|
||||
path: "/albums/:id",
|
||||
component: AlbumsView
|
||||
},
|
||||
{
|
||||
path: "/artists",
|
||||
component: ArtistsView
|
||||
},
|
||||
{
|
||||
path: "/artists/:id",
|
||||
component: ArtistsView
|
||||
},
|
||||
{
|
||||
path: "/radios",
|
||||
component: RadiosView
|
||||
},
|
||||
{
|
||||
path: "/boxes",
|
||||
component: BoxesView
|
||||
},
|
||||
{
|
||||
path: "/boxes/:id",
|
||||
component: BoxesView
|
||||
},
|
||||
{
|
||||
path: "/search",
|
||||
component: SearchView
|
||||
},
|
||||
{
|
||||
path: "/setup",
|
||||
component: SetupView
|
||||
},
|
||||
{
|
||||
path: "/me",
|
||||
component: UsersView
|
||||
},
|
||||
{
|
||||
path: "/favourites",
|
||||
component: FavouritesView
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes
|
||||
});
|
||||
|
||||
export default router;
|
91
src/store/actions.js
Normal file
@ -0,0 +1,91 @@
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
checkIfInstanceIsNew(context) {
|
||||
return new Promise((resolve) => {
|
||||
axios.get(context.rootGetters.server + "/api/system/setup").then((res) => {
|
||||
if (res.status == 200) {
|
||||
resolve(true);
|
||||
} else {
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
loadClientConfigs(context) {
|
||||
return new Promise((resolve) => {
|
||||
axios.get("./config.json").then((res) => {
|
||||
context.state.clientConfig.server = res.data.backend;
|
||||
context.state.clientConfig.isElectron = navigator.userAgent.toLowerCase().includes("electron");
|
||||
if (window.location.hostname.includes("localhost") && !context.getters.isElectron) {
|
||||
context.state.clientConfig.server = res.data.backend_dev;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
},
|
||||
loadSystemDomains(context) {
|
||||
return new Promise((resolve) => {
|
||||
axios.get(context.rootGetters.server + "/api/system/domains", context.rootGetters.headers).then((res) => {
|
||||
context.commit("setSystemConfigDomains", res.data);
|
||||
resolve(res.data);
|
||||
});
|
||||
})
|
||||
},
|
||||
loadSystemSettings(context) {
|
||||
axios.get(context.rootGetters.server + "/api/system").then((res) => {
|
||||
context.commit("setSystemSettings", res.data);
|
||||
});
|
||||
},
|
||||
loadServerInfo(context) {
|
||||
axios.get(context.rootGetters.server + "/api/info").then((res) => {
|
||||
context.commit("setServerInfo", res.data);
|
||||
});
|
||||
},
|
||||
resetViewMenu(context) {
|
||||
context.commit("resetViewMenu");
|
||||
},
|
||||
setNewBackend(context, backend) {
|
||||
axios.post("/settings", { backend: backend }).then(() => {
|
||||
context.commit("setServer", backend);
|
||||
});
|
||||
},
|
||||
setViewMenu(context, menuItems) {
|
||||
context.commit("setViewMenu", menuItems);
|
||||
},
|
||||
startScanningMusic(context) {
|
||||
if (!context.state.serverStatus.scanning_music) {
|
||||
axios.post(context.rootGetters.server + "/api/scan/music", {}, context.rootGetters.headers).then(() => {
|
||||
context.state.serverStatus.scanning_music = true;
|
||||
});
|
||||
}
|
||||
},
|
||||
startScanningVideos(context) {
|
||||
if (!context.state.serverStatus.scanning_video) {
|
||||
axios.post(context.rootGetters.server + "/api/scan/video", {}, context.rootGetters.headers).then(() => {
|
||||
context.state.serverStatus.scanning_video = true;
|
||||
});
|
||||
}
|
||||
},
|
||||
saveSystemAllows(context, allows) {
|
||||
axios
|
||||
.post(context.rootGetters.server + "/api/system", allows, context.rootGetters.headers)
|
||||
.then((res) => {
|
||||
if (res.status == 200) {
|
||||
context.commit("setServerConfigAllows", allows);
|
||||
}
|
||||
});
|
||||
},
|
||||
saveSystemDomains(context, domains) {
|
||||
axios
|
||||
.post(context.rootGetters.server + "/api/system/domains", domains.dynamic, context.rootGetters.headers)
|
||||
.then((res) => {
|
||||
if (res.status == 200) {
|
||||
context.commit("setSystemConfigDomains", domains);
|
||||
}
|
||||
});
|
||||
},
|
||||
resetRedisCache(context) {
|
||||
axios.post(context.rootGetters.server + "/api/system/reset/redis", {}, context.rootGetters.headers);
|
||||
}
|
||||
}
|
31
src/store/getters.js
Normal file
@ -0,0 +1,31 @@
|
||||
import router from '../router'
|
||||
|
||||
export default {
|
||||
headers(state, getters) {
|
||||
return { headers: { authorization: getters["user/token"] } };
|
||||
},
|
||||
server(state) {
|
||||
return state.clientConfig.server;
|
||||
},
|
||||
isElectron(state) {
|
||||
return state.clientConfig.isElectron;
|
||||
},
|
||||
isDialogOpen(state, getters) {
|
||||
return getters["albums/selectedAlbum"]._id || getters["artists/selectedArtist"]._id || getters["boxes/selectedBox"]._id
|
||||
},
|
||||
viewMenu(state, getters) {
|
||||
return state.viewMenu.filter(item => { return item.roles && getters["user/roles"].find(role => { return item.roles.indexOf(role) > -1 }) });
|
||||
},
|
||||
routerPath() {
|
||||
return router.currentRoute._value.path;
|
||||
},
|
||||
routerQuery() {
|
||||
return router.currentRoute._value.query;
|
||||
},
|
||||
serverInfo(state) {
|
||||
return state.serverInfo;
|
||||
},
|
||||
serverConfig(state) {
|
||||
return state.serverConfig;
|
||||
}
|
||||
}
|
36
src/store/index.js
Normal file
@ -0,0 +1,36 @@
|
||||
import { createStore } from 'vuex'
|
||||
|
||||
import state from './state'
|
||||
import getters from './getters'
|
||||
import mutations from './mutations'
|
||||
import actions from "./actions"
|
||||
|
||||
import albums from "./modules/albums/"
|
||||
import artists from "./modules/artists/"
|
||||
import boxes from "./modules/boxes/"
|
||||
import radios from "./modules/radios/"
|
||||
import tracks from "./modules/tracks/"
|
||||
import player from "./modules/player"
|
||||
import user from "./modules/user"
|
||||
import videos from "./modules/videos"
|
||||
import system from "./modules/system"
|
||||
import search from "./modules/search"
|
||||
|
||||
export default createStore({
|
||||
state,
|
||||
getters,
|
||||
mutations,
|
||||
actions,
|
||||
modules: {
|
||||
albums,
|
||||
artists,
|
||||
boxes,
|
||||
radios,
|
||||
player,
|
||||
tracks,
|
||||
user,
|
||||
videos,
|
||||
system,
|
||||
search
|
||||
}
|
||||
})
|
157
src/store/modules/albums/actions.js
Normal file
@ -0,0 +1,157 @@
|
||||
import axios from 'axios'
|
||||
import router from '../../../router'
|
||||
|
||||
export default {
|
||||
clear(context) {
|
||||
context.commit("clear");
|
||||
},
|
||||
filter(context, term) {
|
||||
return new Promise((resolve) => {
|
||||
axios.get(context.rootGetters.server + "/api/albums/filter/" + term, context.rootGetters.headers).then(res => {
|
||||
resolve(res.data);
|
||||
});
|
||||
})
|
||||
},
|
||||
getNextTo(context, album) {
|
||||
return new Promise(resolve => {
|
||||
let i = context.getters.collection.indexOf(album);
|
||||
if (i > -1 && i < context.getters.collection.length - 1) {
|
||||
resolve(context.getters.collection[++i]);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
getPrevTo(context, album) {
|
||||
return new Promise(resolve => {
|
||||
let i = context.getters.collection.indexOf(album);
|
||||
if (i > 0) {
|
||||
resolve(context.getters.collection[--i]);
|
||||
}
|
||||
resolve();
|
||||
})
|
||||
|
||||
},
|
||||
loadAlbums(context, force) {
|
||||
if ((!context.state.eos || force) && !context.state.loading) {
|
||||
context.state.loading = true;
|
||||
axios.get(context.rootGetters.server + "/api/albums/page/" + context.state.page++, context.rootGetters.headers).then((res) => {
|
||||
context.commit("setAlbums", res.data);
|
||||
});
|
||||
}
|
||||
},
|
||||
loadFavourites(context) {
|
||||
axios.get(context.rootGetters.server + "/api/albums/favourites", context.rootGetters.headers).then(res => {
|
||||
if (res.data.length > 0) {
|
||||
context.commit("setAlbums", res.data);
|
||||
}
|
||||
});
|
||||
},
|
||||
loadNewest(context) {
|
||||
axios.get(context.rootGetters.server + "/api/albums/newest/6", context.rootGetters.headers).then((res) => {
|
||||
context.commit("setNewest", res.data);
|
||||
});
|
||||
},
|
||||
loadAlbum(context, id) {
|
||||
context.state.loading = true;
|
||||
return new Promise((resolve) => {
|
||||
axios.get(context.rootGetters.server + "/api/albums/" + id, context.rootGetters.headers).then((res) => {
|
||||
if (res.data != "") {
|
||||
context.commit("setAlbums", [res.data]);
|
||||
} else {
|
||||
context.state.loading = false;
|
||||
}
|
||||
resolve(res.data);
|
||||
})
|
||||
})
|
||||
},
|
||||
resetSelectedAlbum(context) {
|
||||
context.commit("resetSelectedAlbum");
|
||||
router.push("/albums");
|
||||
},
|
||||
move(context, payload) {
|
||||
return new Promise((resolve) => {
|
||||
axios.put(context.rootGetters.server + "/api/albums/" + payload.source + "/move", payload, context.rootGetters.headers).then(res => {
|
||||
resolve(res.data);
|
||||
});
|
||||
})
|
||||
},
|
||||
remove(context, id) {
|
||||
context.commit("remove", id);
|
||||
},
|
||||
selectAlbum(context, album) {
|
||||
if (album.tracks.length == 0) {
|
||||
context.dispatch("loadAlbum", album._id);
|
||||
}
|
||||
|
||||
context.commit('selectAlbum', album);
|
||||
if (context.rootGetters.routerQuery.id != album._id) {
|
||||
let url = "/albums?id=" + album._id;
|
||||
let track = context.rootGetters["tracks/selectedTrack"];
|
||||
if (track._id && track.parentType == "album" && track.parent._id == album._id) {
|
||||
url += "&play=" + track._id
|
||||
}
|
||||
router.push(url);
|
||||
}
|
||||
context.dispatch("preloads");
|
||||
},
|
||||
preloads(context) {
|
||||
// PRELOAD NEXT AND PREV ALBUM
|
||||
let next = context.getters.nextAlbum;
|
||||
if (next._id && next.tracks.length == 0) {
|
||||
context.dispatch("loadAlbum", next._id);
|
||||
}
|
||||
let prev = context.getters.prevAlbum;
|
||||
if (prev._id && prev.tracks.length == 0) {
|
||||
context.dispatch("loadAlbum", prev._id);
|
||||
}
|
||||
},
|
||||
selectAlbumById(context, id) {
|
||||
let album = context.getters.collection.find(item => item._id == id);
|
||||
if (album) {
|
||||
context.dispatch("selectAlbum", album);
|
||||
}
|
||||
},
|
||||
gotoPrevAlbum(context) {
|
||||
let prevAlbum = context.getters.prevAlbum;
|
||||
if (prevAlbum._id) {
|
||||
context.dispatch("selectAlbum", prevAlbum);
|
||||
}
|
||||
},
|
||||
gotoNextAlbum(context) {
|
||||
let nextAlbum = context.getters.nextAlbum;
|
||||
if (nextAlbum._id) {
|
||||
context.dispatch("selectAlbum", nextAlbum);
|
||||
}
|
||||
},
|
||||
uploadNewCover(context, album) {
|
||||
let input = document.createElement('input');
|
||||
input.type = "file";
|
||||
input.accept = "image/jpeg, image/png";
|
||||
input.addEventListener("change", function () {
|
||||
if (input.value) {
|
||||
let formData = new FormData();
|
||||
formData.append("file", input.files[0]);
|
||||
let h = context.rootGetters.headers;
|
||||
h.headers["content-type"] = "multipart/form-data";
|
||||
axios
|
||||
.put(context.rootGetters.server + "/api/albums/" + album._id + "/cover", formData, context.rootGetters.headers)
|
||||
.then(res => {
|
||||
album.covers = res.data;
|
||||
});
|
||||
}
|
||||
});
|
||||
input.click();
|
||||
},
|
||||
resetCover(context, album) {
|
||||
axios.delete(context.rootGetters.server + "/api/albums/" + album._id + "/cover", context.rootGetters.headers).then(() => {
|
||||
album.covers = {}
|
||||
});
|
||||
},
|
||||
updateAlbum(context, album) {
|
||||
let body = {
|
||||
_id: album._id,
|
||||
visibility: album.visibility
|
||||
}
|
||||
axios.put(context.rootGetters.server + "/api/albums/" + album._id, body, context.rootGetters.headers);
|
||||
}
|
||||
}
|
36
src/store/modules/albums/getters.js
Normal file
@ -0,0 +1,36 @@
|
||||
export default {
|
||||
collection(state) {
|
||||
return state.collection;
|
||||
},
|
||||
favourites(state, getters, rootState, rootGetters) {
|
||||
return state.collection.filter(f => rootGetters["user/favourites"].map(m => m.itemId).indexOf(f._id) > -1);
|
||||
},
|
||||
nextAlbum(state) {
|
||||
let currentIndex = state.collection.indexOf(state.selectedAlbum);
|
||||
let nextAlbum = {};
|
||||
if (state.collection.length > currentIndex + 1) {
|
||||
nextAlbum = state.collection[currentIndex + 1];
|
||||
}
|
||||
return nextAlbum;
|
||||
},
|
||||
prevAlbum(state) {
|
||||
let currentIndex = state.collection.indexOf(state.selectedAlbum);
|
||||
let prevAlbum = {};
|
||||
if (currentIndex > 0) {
|
||||
prevAlbum = state.collection[currentIndex - 1];
|
||||
}
|
||||
return prevAlbum;
|
||||
},
|
||||
selectedAlbum(state) {
|
||||
return state.selectedAlbum;
|
||||
},
|
||||
loading(state) {
|
||||
return state.loading;
|
||||
},
|
||||
eos(state) {
|
||||
return state.eos;
|
||||
},
|
||||
newest(state) {
|
||||
return state.newest;
|
||||
}
|
||||
}
|
12
src/store/modules/albums/index.js
Normal file
@ -0,0 +1,12 @@
|
||||
import state from './state.js';
|
||||
import getters from './getters.js';
|
||||
import mutations from './mutations.js';
|
||||
import actions from './actions.js';
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
mutations,
|
||||
actions
|
||||
}
|
79
src/store/modules/albums/mutations.js
Normal file
@ -0,0 +1,79 @@
|
||||
export default {
|
||||
clear(state) {
|
||||
state.collection = [];
|
||||
state.newest = [];
|
||||
state.loading = false;
|
||||
state.eos = false;
|
||||
state.page = 1;
|
||||
},
|
||||
remove(state, id) {
|
||||
let album = state.collection.find(f => f._id == id);
|
||||
if (album) {
|
||||
let i = state.collection.indexOf(album);
|
||||
state.collection.splice(i, 1);
|
||||
}
|
||||
},
|
||||
resetSelectedAlbum(state) {
|
||||
if (state.selectedAlbum._id)
|
||||
state.selectedAlbum = { tracks: [], covers: {} };
|
||||
},
|
||||
selectAlbum(state, album) {
|
||||
if (state.selectedAlbum._id != album._id) {
|
||||
state.selectedAlbum = album;
|
||||
}
|
||||
},
|
||||
setAlbums(state, albums) {
|
||||
if (albums.length == 0) {
|
||||
state.eos = true;
|
||||
state.loading = false;
|
||||
if (state.page > 1) {
|
||||
state.page--;
|
||||
}
|
||||
return;
|
||||
}
|
||||
albums.forEach(album => {
|
||||
let existsAlbum = state.collection.find(f => f._id == album._id);
|
||||
if (!existsAlbum) {
|
||||
let item = state.collection.find((item) => {
|
||||
if (item.artist_name > album.artist_name
|
||||
|| item.artist_name == album.artist_name && item.year > album.year
|
||||
|| item.artist_name == album.artist_name && item.year == album.year && item.title > album.title) {
|
||||
return item;
|
||||
}
|
||||
})
|
||||
|
||||
if (!album.covers) {
|
||||
album.covers = {};
|
||||
}
|
||||
|
||||
if (item) {
|
||||
let index = state.collection.indexOf(item);
|
||||
state.collection.splice(index, 0, album);
|
||||
} else {
|
||||
state.collection.push(album);
|
||||
}
|
||||
album.tracks.forEach((track) => {
|
||||
track.parent = album;
|
||||
track.parentType = "album"
|
||||
});
|
||||
|
||||
} else if (existsAlbum && album.tracks.length > 0) {
|
||||
existsAlbum.covers = album.covers || {};
|
||||
existsAlbum.tracks = album.tracks;
|
||||
existsAlbum.tracks.forEach((track) => {
|
||||
track.parent = existsAlbum;
|
||||
track.parentType = "album"
|
||||
});
|
||||
}
|
||||
});
|
||||
state.loading = false;
|
||||
},
|
||||
setNewest(state, albums) {
|
||||
albums.forEach(album => {
|
||||
if (!album.covers) {
|
||||
album.covers = {};
|
||||
}
|
||||
});
|
||||
state.newest = albums;
|
||||
}
|
||||
}
|
8
src/store/modules/albums/state.js
Normal file
@ -0,0 +1,8 @@
|
||||
export default {
|
||||
collection: [],
|
||||
newest: [],
|
||||
selectedAlbum: { tracks: [], covers: {} },
|
||||
page: 1,
|
||||
loading: false,
|
||||
eos: false
|
||||
}
|
123
src/store/modules/artists/actions.js
Normal file
@ -0,0 +1,123 @@
|
||||
import axios from 'axios'
|
||||
import router from '../../../router'
|
||||
|
||||
export default {
|
||||
clear(context) {
|
||||
context.commit("clear");
|
||||
},
|
||||
filter(context, term) {
|
||||
return new Promise((resolve) => {
|
||||
axios.get(context.rootGetters.server + "/api/artists/filter/" + term, context.rootGetters.headers).then(res => {
|
||||
resolve(res.data);
|
||||
});
|
||||
})
|
||||
},
|
||||
loadArtists(context, force) {
|
||||
if ((!context.state.eos || force) && !context.state.loading) {
|
||||
context.state.loading = true;
|
||||
axios.get(context.rootGetters.server + "/api/artists/page/" + context.state.page++, context.rootGetters.headers).then((res) => {
|
||||
context.commit("setArtists", res.data);
|
||||
});
|
||||
}
|
||||
},
|
||||
loadArtist(context, id) {
|
||||
context.state.loading = true;
|
||||
return new Promise((resolve) => {
|
||||
axios.get(context.rootGetters.server + "/api/artists/" + id, context.rootGetters.headers).then((res) => {
|
||||
if (res.data != "") {
|
||||
context.commit("setArtists", [res.data]);
|
||||
} else {
|
||||
context.state.loading = false;
|
||||
}
|
||||
resolve(res.data);
|
||||
});
|
||||
});
|
||||
},
|
||||
loadFavourites(context) {
|
||||
axios.get(context.rootGetters.server + "/api/artists/favourites", context.rootGetters.headers).then(res => {
|
||||
if (res.data.length > 0) {
|
||||
context.commit("setArtists", res.data);
|
||||
}
|
||||
});
|
||||
},
|
||||
selectArtist(context, artist) {
|
||||
if (artist.albums.length == 0) {
|
||||
context.dispatch("loadArtist", artist._id)
|
||||
}
|
||||
context.commit('selectArtist', artist);
|
||||
if (context.rootGetters.routerQuery.id != artist._id) {
|
||||
let url = "/artists?id=" + artist._id;
|
||||
let track = context.rootGetters["tracks/selectedTrack"];
|
||||
if (track._id && track.parentType == "artist" && track.parent.parent._id == artist._id) {
|
||||
url += "&play=" + track._id
|
||||
}
|
||||
router.push(url);
|
||||
}
|
||||
|
||||
// PRELOAD NEXT AND PREV ARTIST
|
||||
let next = context.getters.nextArtist;
|
||||
if (next._id && next.albums.length == 0) {
|
||||
context.dispatch("loadArtist", next._id);
|
||||
}
|
||||
let prev = context.getters.prevArtist;
|
||||
if (prev._id && prev.albums.length == 0) {
|
||||
context.dispatch("loadArtist", prev._id);
|
||||
}
|
||||
},
|
||||
selectArtistById(context, id) {
|
||||
let artist = context.getters.collection.find(item => item._id == id);
|
||||
if (artist && artist._id != context.getters.selectedArtist._id) {
|
||||
context.dispatch("selectArtist", artist);
|
||||
}
|
||||
},
|
||||
resetSelectedArtist(context) {
|
||||
context.commit("resetSelectedArtist");
|
||||
router.push("/artists");
|
||||
},
|
||||
move(context, payload) {
|
||||
return new Promise((resolve) => {
|
||||
axios.put(context.rootGetters.server + "/api/artists/" + payload.source + "/move", payload, context.rootGetters.headers).then(res => {
|
||||
resolve(res.data);
|
||||
});
|
||||
})
|
||||
},
|
||||
remove(context, id) {
|
||||
context.commit("remove", id);
|
||||
},
|
||||
gotoPrevArtist(context) {
|
||||
let prevArtist = context.getters.prevArtist;
|
||||
if (prevArtist._id) {
|
||||
context.dispatch("selectArtist", prevArtist);
|
||||
}
|
||||
},
|
||||
gotoNextArtist(context) {
|
||||
let nextArtist = context.getters.nextArtist;
|
||||
if (nextArtist._id) {
|
||||
context.dispatch("selectArtist", nextArtist);
|
||||
}
|
||||
},
|
||||
uploadNewCover(context, artist) {
|
||||
let input = document.createElement('input');
|
||||
input.type = "file";
|
||||
input.accept = "image/jpeg, image/png";
|
||||
input.addEventListener("change", function () {
|
||||
if (input.value) {
|
||||
let formData = new FormData();
|
||||
formData.append("file", input.files[0]);
|
||||
let h = context.rootGetters.headers;
|
||||
h.headers["content-type"] = "multipart/form-data";
|
||||
axios
|
||||
.put(context.rootGetters.server + "/api/artists/" + artist._id + "/cover", formData, context.rootGetters.headers)
|
||||
.then(res => {
|
||||
artist.covers = res.data;
|
||||
});
|
||||
}
|
||||
});
|
||||
input.click();
|
||||
},
|
||||
resetCover(context, artist) {
|
||||
axios.delete(context.rootGetters.server + "/api/artists/" + artist._id + "/cover", context.rootGetters.headers).then(() => {
|
||||
artist.covers = {};
|
||||
});
|
||||
},
|
||||
}
|
33
src/store/modules/artists/getters.js
Normal file
@ -0,0 +1,33 @@
|
||||
export default {
|
||||
collection(state) {
|
||||
return state.collection;
|
||||
},
|
||||
favourites(state, getters, rootState, rootGetters) {
|
||||
return state.collection.filter(f => rootGetters["user/favourites"].map(m => m.itemId).indexOf(f._id) > -1);
|
||||
},
|
||||
prevArtist(state) {
|
||||
let currentIndex = state.collection.indexOf(state.selectedArtist);
|
||||
let prevArtist = {};
|
||||
if (currentIndex > 0) {
|
||||
prevArtist = state.collection[currentIndex - 1];
|
||||
}
|
||||
return prevArtist;
|
||||
},
|
||||
nextArtist(state) {
|
||||
let currentIndex = state.collection.indexOf(state.selectedArtist);
|
||||
let nextArtist = {};
|
||||
if (state.collection.length > currentIndex + 1) {
|
||||
nextArtist = state.collection[currentIndex + 1];
|
||||
}
|
||||
return nextArtist;
|
||||
},
|
||||
selectedArtist(state) {
|
||||
return state.selectedArtist;
|
||||
},
|
||||
loading(state) {
|
||||
return state.loading;
|
||||
},
|
||||
eos(state) {
|
||||
return state.eos;
|
||||
}
|
||||
}
|
12
src/store/modules/artists/index.js
Normal file
@ -0,0 +1,12 @@
|
||||
import state from './state.js';
|
||||
import getters from './getters.js';
|
||||
import mutations from './mutations.js';
|
||||
import actions from './actions.js';
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
mutations,
|
||||
actions
|
||||
}
|
80
src/store/modules/artists/mutations.js
Normal file
@ -0,0 +1,80 @@
|
||||
export default {
|
||||
clear(state) {
|
||||
state.collection = [];
|
||||
state.loading = false;
|
||||
state.eos = false;
|
||||
state.page = 1;
|
||||
},
|
||||
remove(state, id) {
|
||||
let artist = state.collection.find(f => f._id == id);
|
||||
if (artist) {
|
||||
let i = state.collection.indexOf(artist);
|
||||
state.collection.splice(i, 1);
|
||||
}
|
||||
},
|
||||
resetSelectedArtist(state) {
|
||||
if (state.selectedArtist._id)
|
||||
state.selectedArtist = { albums: [], tracks: [], covers: {} };
|
||||
},
|
||||
setArtists(state, artists) {
|
||||
if (artists.length == 0) {
|
||||
state.eos = true;
|
||||
state.loading = false;
|
||||
if (state.page > 1) {
|
||||
state.page--;
|
||||
}
|
||||
return;
|
||||
}
|
||||
artists.forEach(artist => {
|
||||
let existsArtist = state.collection.find(f => f._id == artist._id);
|
||||
if (!existsArtist) {
|
||||
let item = state.collection.find((item) => {
|
||||
if (item.name > artist.name) {
|
||||
return item;
|
||||
}
|
||||
})
|
||||
|
||||
if (!artist.covers) {
|
||||
artist.covers = {};
|
||||
}
|
||||
if (item) {
|
||||
let index = state.collection.indexOf(item);
|
||||
state.collection.splice(index, 0, artist);
|
||||
} else {
|
||||
state.collection.push(artist);
|
||||
}
|
||||
|
||||
artist.albums.forEach((album) => {
|
||||
album.parent = artist;
|
||||
if (!album.covers) {
|
||||
album.covers = {};
|
||||
}
|
||||
album.tracks.forEach((track) => {
|
||||
track.parent = album;
|
||||
track.parentType = "artist"
|
||||
artist.tracks.push(track);
|
||||
});
|
||||
});
|
||||
|
||||
} else if (existsArtist && artist.albums.length > 0) {
|
||||
existsArtist.tracks = [];
|
||||
existsArtist.covers = artist.covers || {};
|
||||
existsArtist.albums = artist.albums;
|
||||
existsArtist.albums.forEach((album) => {
|
||||
album.parent = existsArtist;
|
||||
album.tracks.forEach((track) => {
|
||||
track.parent = album;
|
||||
track.parentType = "artist"
|
||||
existsArtist.tracks.push(track);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
state.loading = false;
|
||||
},
|
||||
selectArtist(state, artist) {
|
||||
if (state.selectedArtist._id != artist._id) {
|
||||
state.selectedArtist = artist;
|
||||
}
|
||||
},
|
||||
}
|
7
src/store/modules/artists/state.js
Normal file
@ -0,0 +1,7 @@
|
||||
export default {
|
||||
collection: [],
|
||||
selectedArtist: { artists: [], tracks: [], covers: {} },
|
||||
page: 1,
|
||||
loading: false,
|
||||
eos: false
|
||||
}
|
133
src/store/modules/boxes/actions.js
Normal file
@ -0,0 +1,133 @@
|
||||
import axios from 'axios'
|
||||
import router from '../../../router'
|
||||
|
||||
export default {
|
||||
clear(context) {
|
||||
context.commit("clear");
|
||||
},
|
||||
filter(context, term) {
|
||||
return new Promise((resolve) => {
|
||||
axios.get(context.rootGetters.server + "/api/boxes/filter/" + term, context.rootGetters.headers).then(res => {
|
||||
resolve(res.data);
|
||||
});
|
||||
})
|
||||
},
|
||||
loadBoxes(context, force) {
|
||||
if ((!context.state.eos || force) && !context.state.loading) {
|
||||
context.state.loading = true;
|
||||
axios.get(context.rootGetters.server + "/api/boxes/page/" + context.state.page++, context.rootGetters.headers).then((res) => {
|
||||
context.commit("setBoxes", res.data);
|
||||
});
|
||||
}
|
||||
},
|
||||
loadFavourites(context) {
|
||||
axios.get(context.rootGetters.server + "/api/boxes/favourites", context.rootGetters.headers).then(res => {
|
||||
if (res.data.length > 0) {
|
||||
context.commit("setBoxes", res.data);
|
||||
}
|
||||
});
|
||||
},
|
||||
loadNewest(context) {
|
||||
axios.get(context.rootGetters.server + "/api/boxes/newest/6", context.rootGetters.headers).then((res) => {
|
||||
context.commit("setNewest", res.data);
|
||||
});
|
||||
},
|
||||
loadBox(context, id) {
|
||||
context.state.loading = true;
|
||||
axios.get(context.rootGetters.server + "/api/boxes/" + id, context.rootGetters.headers).then((res) => {
|
||||
if (res.data != "") {
|
||||
context.commit("setBoxes", [res.data]);
|
||||
} else {
|
||||
context.state.loading = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
resetSelectedBox(context) {
|
||||
context.commit("resetSelectedBox");
|
||||
router.push("/boxes");
|
||||
},
|
||||
selectBox(context, box) {
|
||||
if (box.videos.length == 0) {
|
||||
context.dispatch("loadBox", box._id)
|
||||
}
|
||||
|
||||
context.commit('selectBox', box);
|
||||
if (!context.rootGetters.routerQuery || context.rootGetters.routerQuery.id != box._id) {
|
||||
router.push("/boxes?id=" + box._id);
|
||||
}
|
||||
|
||||
context.dispatch("preload")
|
||||
},
|
||||
preload(context) {
|
||||
// PRELOAD NEXT AND PREV ALBUM
|
||||
let next = context.getters.nextBox;
|
||||
if (next._id && next.videos.length == 0) {
|
||||
context.dispatch("loadBox", next._id);
|
||||
}
|
||||
let prev = context.getters.prevBox;
|
||||
if (prev._id && prev.videos.length == 0) {
|
||||
context.dispatch("loadBox", prev._id);
|
||||
}
|
||||
},
|
||||
selectBoxById(context, id) {
|
||||
let box = context.getters.collection.find(item => item._id == id);
|
||||
if (box) {
|
||||
context.dispatch("selectBox", box);
|
||||
}
|
||||
},
|
||||
gotoPrevBox(context) {
|
||||
let prevBox = context.getters.prevBox;
|
||||
if (prevBox._id) {
|
||||
context.dispatch("selectBox", prevBox);
|
||||
}
|
||||
},
|
||||
move(context, payload) {
|
||||
return new Promise((resolve) => {
|
||||
axios
|
||||
.put(context.rootGetters.server + "/api/boxes/" + payload.source + "/move", payload, context.rootGetters.headers)
|
||||
.then(res => {
|
||||
resolve(res.data);
|
||||
});
|
||||
})
|
||||
},
|
||||
remove(context, id) {
|
||||
context.commit("remove", id);
|
||||
},
|
||||
gotoNextBox(context) {
|
||||
let nextBox = context.getters.nextBox;
|
||||
if (nextBox._id) {
|
||||
context.dispatch("selectBox", nextBox);
|
||||
}
|
||||
},
|
||||
updateBox(context, box) {
|
||||
let body = {
|
||||
_id: box._id,
|
||||
visibility: box.visibility
|
||||
}
|
||||
axios.put(context.rootGetters.server + "/api/boxes/" + box._id, body, context.rootGetters.headers);
|
||||
},
|
||||
uploadNewCover(context, box) {
|
||||
let input = document.createElement('input');
|
||||
input.type = "file";
|
||||
input.accept = "image/jpeg, image/png";
|
||||
input.addEventListener("change", function () {
|
||||
if (input.value) {
|
||||
let formData = new FormData();
|
||||
formData.append("file", input.files[0]);
|
||||
let h = context.rootGetters.headers;
|
||||
h.headers["content-type"] = "multipart/form-data";
|
||||
axios
|
||||
.put(context.rootGetters.server + "/api/boxes/" + box._id + "/cover", formData, context.rootGetters.headers)
|
||||
.then(res => {
|
||||
box.covers = res.data;
|
||||
});
|
||||
}
|
||||
});
|
||||
input.click();
|
||||
},
|
||||
resetCover(context, box) {
|
||||
axios.delete(context.rootGetters.server + "/api/boxes/" + box._id + "/cover", context.rootGetters.headers).then(() => {
|
||||
box.covers = {}
|
||||
});
|
||||
},
|
||||
}
|