-
-
+
-
-
- {% include "aircox_streamer/source_item.html" %}
-
-
+
+
+ {% include "aircox_streamer/source_item.html" %}
+
+
+
+
{% endblock %}
diff --git a/assets/README.md b/assets/README.md
new file mode 100644
index 0000000..3a49b04
--- /dev/null
+++ b/assets/README.md
@@ -0,0 +1,24 @@
+# aircox-assets
+
+## Project setup
+```
+npm install
+```
+
+### Compiles and hot-reloads for development
+```
+npm run serve
+```
+
+### Compiles and minifies for production
+```
+npm run build
+```
+
+### Lints and fixes files
+```
+npm run lint
+```
+
+### Customize configuration
+See [Configuration Reference](https://cli.vuejs.org/config/).
diff --git a/assets/admin/admin.scss b/assets/admin/admin.scss
deleted file mode 100644
index 4e7236e..0000000
--- a/assets/admin/admin.scss
+++ /dev/null
@@ -1,29 +0,0 @@
-
-.navbar .navbar-brand {
- padding-right: 1em;
-}
-
-.navbar .navbar-brand img {
- margin: 0em 0.4em;
- margin-top: 0.3em;
- max-height: 3em;
-}
-
-.breadcrumbs {
- margin-bottom: 1em;
-}
-
-.results > #result_list {
- width: 100%;
- margin: 1em 0em;
-}
-
-
-ul.menu-list li {
- list-style-type: none;
-}
-
-.submit-row a.deletelink {
- height: 35px;
-}
-
diff --git a/assets/admin/app.js b/assets/admin/app.js
deleted file mode 100644
index 53501c6..0000000
--- a/assets/admin/app.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import App from 'public/app';
-import AStatistics from './statistics.vue';
-
-export default {
- ...App,
- components: {...App.components, AStatistics},
-}
-
-
diff --git a/assets/admin/index.js b/assets/admin/index.js
deleted file mode 100644
index 5e97cc1..0000000
--- a/assets/admin/index.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import '@fortawesome/fontawesome-free/css/all.min.css'
-import '@fortawesome/fontawesome-free/css/fontawesome.min.css'
-
-import AdminApp from './app';
-import './admin.scss';
-
-window.aircox_admin = {
- /**
- * Filter items in the parent navbar-dropdown for provided key event on text input
- */
- filter_menu: function(event) {
- var filter = new RegExp(event.target.value, 'gi');
- var container = event.target.closest('.navbar-dropdown');
-
- if(event.target.value)
- for(var item of container.querySelectorAll('a.navbar-item'))
- item.style.display = item.innerHTML.search(filter) == -1 ? 'none' : null;
- else
- for(var item of container.querySelectorAll('a.navbar-item'))
- item.style.display = null;
- },
-}
-
-window.AdminApp = AdminApp
-
diff --git a/assets/babel.config.js b/assets/babel.config.js
new file mode 100644
index 0000000..e955840
--- /dev/null
+++ b/assets/babel.config.js
@@ -0,0 +1,5 @@
+module.exports = {
+ presets: [
+ '@vue/cli-plugin-babel/preset'
+ ]
+}
diff --git a/assets/jsconfig.json b/assets/jsconfig.json
new file mode 100644
index 0000000..4aafc5f
--- /dev/null
+++ b/assets/jsconfig.json
@@ -0,0 +1,19 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "module": "esnext",
+ "baseUrl": "./",
+ "moduleResolution": "node",
+ "paths": {
+ "@/*": [
+ "src/*"
+ ]
+ },
+ "lib": [
+ "esnext",
+ "dom",
+ "dom.iterable",
+ "scripthost"
+ ]
+ }
+}
diff --git a/assets/package.json b/assets/package.json
new file mode 100644
index 0000000..b95a798
--- /dev/null
+++ b/assets/package.json
@@ -0,0 +1,47 @@
+{
+ "name": "aircox-assets",
+ "version": "0.1.0",
+ "private": true,
+ "sideEffects": true,
+ "scripts": {
+ "serve": "vue-cli-service serve",
+ "build": "vue-cli-service build",
+ "lint": "vue-cli-service lint"
+ },
+ "dependencies": {
+ "@fortawesome/fontawesome-free": "^6.0.0",
+ "core-js": "^3.8.3",
+ "vue": "^3.2.13"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.12.16",
+ "@babel/eslint-parser": "^7.12.16",
+ "@vue/cli-plugin-babel": "~5.0.0",
+ "@vue/cli-plugin-eslint": "~5.0.0",
+ "@vue/cli-service": "~5.0.0",
+ "eslint": "^7.32.0",
+ "eslint-plugin-vue": "^8.0.3",
+ "sass-loader": "^12.6.0",
+ "bulma": "^0.9.3"
+ },
+ "eslintConfig": {
+ "root": true,
+ "env": {
+ "node": true
+ },
+ "extends": [
+ "plugin:vue/vue3-essential",
+ "eslint:recommended"
+ ],
+ "parserOptions": {
+ "parser": "@babel/eslint-parser"
+ },
+ "rules": {}
+ },
+ "browserslist": [
+ "> 1%",
+ "last 2 versions",
+ "not dead",
+ "not ie 11"
+ ]
+}
diff --git a/assets/public/app.js b/assets/public/app.js
deleted file mode 100644
index b447601..0000000
--- a/assets/public/app.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import AAutocomplete from './autocomplete'
-import AEpisode from './episode'
-import APlayer from './player'
-import APlaylist from './playlist'
-import ASoundItem from './soundItem'
-
-const App = {
- el: '#app',
- delimiters: ['[[', ']]'],
-
- computed: {
- player() { return window.aircox.player; },
- },
-
- components: {AAutocomplete, AEpisode, APlaylist, ASoundItem},
-}
-
-export const PlayerApp = {
- el: '#player',
- components: {APlayer},
-}
-
-export default App
-
-
diff --git a/assets/public/logo.png b/assets/public/logo.png
new file mode 100644
index 0000000..4b71388
Binary files /dev/null and b/assets/public/logo.png differ
diff --git a/assets/public/vue.esm-browser.js b/assets/public/vue.esm-browser.js
new file mode 120000
index 0000000..ec53caf
--- /dev/null
+++ b/assets/public/vue.esm-browser.js
@@ -0,0 +1 @@
+../node_modules/vue/dist/vue.esm-browser.js
\ No newline at end of file
diff --git a/assets/public/vue.esm-browser.prod.js b/assets/public/vue.esm-browser.prod.js
new file mode 120000
index 0000000..3ab9489
--- /dev/null
+++ b/assets/public/vue.esm-browser.prod.js
@@ -0,0 +1 @@
+../node_modules/vue/dist/vue.esm-browser.prod.js
\ No newline at end of file
diff --git a/assets/src/admin.js b/assets/src/admin.js
new file mode 100644
index 0000000..8f0c6e9
--- /dev/null
+++ b/assets/src/admin.js
@@ -0,0 +1,15 @@
+import './assets/admin.scss'
+import './index.js'
+
+import App from './app';
+import {admin as components} from './components'
+
+const AdminApp = {
+ ...App,
+ components: {...App.components, ...components},
+}
+export default AdminApp;
+
+
+window.App = AdminApp
+
diff --git a/assets/src/app.js b/assets/src/app.js
new file mode 100644
index 0000000..931bc0d
--- /dev/null
+++ b/assets/src/app.js
@@ -0,0 +1,20 @@
+import components from './components'
+
+const App = {
+ el: '#app',
+ delimiters: ['[[', ']]'],
+ components: {...components},
+
+ computed: {
+ player() { return window.aircox.player; },
+ },
+}
+
+export const PlayerApp = {
+ el: '#player',
+ components: {...components},
+}
+
+export default App
+
+
diff --git a/assets/public/appBuilder.js b/assets/src/appBuilder.js
similarity index 96%
rename from assets/public/appBuilder.js
rename to assets/src/appBuilder.js
index 607ce41..b86f3b4 100644
--- a/assets/public/appBuilder.js
+++ b/assets/src/appBuilder.js
@@ -15,11 +15,11 @@ export default class Builder {
/**
* Fetch app from remote and mount application.
*/
- fetch(url, {el='app', ...options}={}) {
+ fetch(url, {el='#app', ...options}={}) {
return fetch(url, options).then(response => response.text())
.then(content => {
let doc = new DOMParser().parseFromString(content, 'text/html')
- let app = doc.getElementById('app')
+ let app = doc.querySelector(el)
content = app ? app.innerHTML : content
return this.mount({content, title: doc.title, reset:true, url })
})
@@ -102,7 +102,7 @@ export default class Builder {
else
options = {...options, method: target.method, body: formData}
}
- this.fetch(url, options).then(_ => this.historySave(url))
+ this.fetch(url, options).then(() => this.historySave(url))
event.preventDefault();
event.stopPropagation();
}
diff --git a/assets/src/assets/admin.scss b/assets/src/assets/admin.scss
new file mode 100644
index 0000000..269ebfd
--- /dev/null
+++ b/assets/src/assets/admin.scss
@@ -0,0 +1,31 @@
+
+#app.admin {
+ .navbar .navbar-brand {
+ padding-right: 1em;
+ }
+
+ .navbar .navbar-brand img {
+ margin: 0em 0.4em;
+ margin-top: 0.3em;
+ max-height: 3em;
+ }
+
+ .breadcrumbs {
+ margin-bottom: 1em;
+ }
+
+ .results > #result_list {
+ width: 100%;
+ margin: 1em 0em;
+ }
+
+
+ ul.menu-list li {
+ list-style-type: none;
+ }
+
+ .submit-row a.deletelink {
+ height: 35px;
+ }
+}
+
diff --git a/assets/public/styles.scss b/assets/src/assets/styles.scss
similarity index 98%
rename from assets/public/styles.scss
rename to assets/src/assets/styles.scss
index c019c55..860225a 100644
--- a/assets/public/styles.scss
+++ b/assets/src/assets/styles.scss
@@ -2,9 +2,10 @@
@import "~bulma/sass/utilities/_all.sass";
@import "~bulma/sass/components/dropdown.sass";
+@import './admin.scss';
+
$body-background-color: $light;
-@import "~buefy/src/scss/components/_autocomplete.scss";
@import "~bulma";
//-- helpers/modifiers
diff --git a/assets/public/autocomplete.vue b/assets/src/components/AAutocomplete.vue
similarity index 93%
rename from assets/public/autocomplete.vue
rename to assets/src/components/AAutocomplete.vue
index d7680f3..0e45f02 100644
--- a/assets/public/autocomplete.vue
+++ b/assets/src/components/AAutocomplete.vue
@@ -1,9 +1,8 @@
-
diff --git a/assets/src/components/index.js b/assets/src/components/index.js
new file mode 100644
index 0000000..9272a22
--- /dev/null
+++ b/assets/src/components/index.js
@@ -0,0 +1,23 @@
+import AAutocomplete from './AAutocomplete.vue'
+import AEpisode from './AEpisode.vue'
+import AList from './AList.vue'
+import APage from './APage.vue'
+import APlayer from './APlayer.vue'
+import APlaylist from './APlaylist.vue'
+import AProgress from './AProgress.vue'
+import ASoundItem from './ASoundItem.vue'
+import AStatistics from './AStatistics.vue'
+import AStreamer from './AStreamer.vue'
+
+/**
+ * Core components
+ */
+export default {
+ AAutocomplete, AEpisode, AList, APage, APlayer, APlaylist,
+ AProgress, ASoundItem,
+}
+
+export const admin = {
+ AStatistics, AStreamer,
+}
+
diff --git a/assets/src/core.js b/assets/src/core.js
new file mode 100644
index 0000000..eb120b7
--- /dev/null
+++ b/assets/src/core.js
@@ -0,0 +1,8 @@
+import './index.js'
+import App from './app.js'
+
+export default App
+
+window.App = App
+
+
diff --git a/assets/public/index.js b/assets/src/index.js
similarity index 79%
rename from assets/public/index.js
rename to assets/src/index.js
index c710d44..167bbc5 100644
--- a/assets/public/index.js
+++ b/assets/src/index.js
@@ -13,7 +13,7 @@ import Builder from './appBuilder'
import Sound from './sound'
import {Set} from './model'
-import './styles.scss'
+import './assets/styles.scss'
window.aircox = {
@@ -32,14 +32,17 @@ window.aircox = {
/**
* Initialize main application and player.
*/
- init(props=null, {config=null, builder=null, initPlayer=true}={}) {
+ init(props=null, {config=null, builder=null, initPlayer=true, hotReload=false}={}) {
builder = builder || this.builder
this.builder = builder
- if(config)
- builder.config = config
+ if(config || window.App)
+ builder.config = config || window.App
builder.title = document.title
builder.mount({props})
+ if(hotReload)
+ builder.enableHotReload(hotReload)
+
if(initPlayer) {
let playerBuilder = this.playerBuilder
playerBuilder.mount()
@@ -47,14 +50,3 @@ window.aircox = {
},
}
-/*
-window.addEventListener('load', e => {
- const [app, player] = [aircox.builder, aircox.playerBuilder]
- app.title = document.title
- app.mount()
- app.enableHotReload(window)
-
- player.mount()
-})
-*/
-
diff --git a/assets/public/live.js b/assets/src/live.js
similarity index 93%
rename from assets/public/live.js
rename to assets/src/live.js
index 1c9679c..f083f77 100644
--- a/assets/public/live.js
+++ b/assets/src/live.js
@@ -1,4 +1,4 @@
-import {setEcoTimeout} from 'public/utils';
+import {setEcoTimeout} from './utils';
import Model from './model';
export default class Live {
@@ -38,7 +38,7 @@ export default class Live {
refresh() {
const promise = this.fetch();
- promise.then(data => {
+ promise.then(() => {
if(promise != this.promise)
return [];
diff --git a/assets/public/model.js b/assets/src/model.js
similarity index 87%
rename from assets/public/model.js
rename to assets/src/model.js
index d945c07..51c0200 100644
--- a/assets/public/model.js
+++ b/assets/src/model.js
@@ -1,15 +1,24 @@
-function getCookie(name) {
+/**
+ * Return cookie with provided key
+ */
+function getCookie(key) {
if(document.cookie && document.cookie !== '') {
const cookie = document.cookie.split(';')
- .find(c => c.trim().startsWith(name + '='))
+ .find(c => c.trim().startsWith(key + '='))
return cookie ? decodeURIComponent(cookie.split('=')[1]) : null;
}
return null;
}
+/**
+ * CSRF token provided by Django
+ */
var csrfToken = null;
+/**
+ * Get CSRF token
+ */
export function getCsrf() {
if(csrfToken === null)
csrfToken = getCookie('csrftoken')
@@ -18,9 +27,17 @@ export function getCsrf() {
// TODO: prevent duplicate simple fetch
+/**
+ * Provide interface used to fetch and manipulate objects.
+ */
export default class Model {
- constructor(data, {url=null}={}) {
+ /**
+ * Instanciate model with provided data and options.
+ * By default `url` is taken from `data.url_`.
+ */
+ constructor(data, {url=null, ...options}={}) {
this.url = url || data.url_;
+ this.options = options;
this.commit(data);
}
@@ -45,8 +62,13 @@ export default class Model {
}
}
- static fromList(items, args=null) {
- return items ? items.map(d => new this(d, args)) : []
+ /**
+ * Return model instances for the provided list of model data.
+ * @param {Array} items: array of data
+ * @param {Object} options: options passed down to all model instances
+ */
+ static fromList(items, options=null) {
+ return items ? items.map(d => new this(d, options)) : []
}
/**
diff --git a/assets/public/sound.js b/assets/src/sound.js
similarity index 82%
rename from assets/public/sound.js
rename to assets/src/sound.js
index 10e7511..1e7035b 100644
--- a/assets/public/sound.js
+++ b/assets/src/sound.js
@@ -1,4 +1,4 @@
-import Model, {Set} from './model';
+import Model from './model';
export default class Sound extends Model {
diff --git a/assets/streamer/controllers.js b/assets/src/streamer.js
similarity index 96%
rename from assets/streamer/controllers.js
rename to assets/src/streamer.js
index 3137cc3..e5a5787 100644
--- a/assets/streamer/controllers.js
+++ b/assets/src/streamer.js
@@ -1,5 +1,5 @@
-import Model from 'public/model';
-import {setEcoInterval} from 'public/utils';
+import Model from './model';
+import {setEcoInterval} from './utils';
export class Streamer extends Model {
@@ -18,6 +18,8 @@ export class Streamer extends Model {
}
}
+export default Streamer;
+
export class Request extends Model {
static getId(data) { return data.rid; }
}
diff --git a/assets/streamer/app.js b/assets/src/streamer/app.js
similarity index 88%
rename from assets/streamer/app.js
rename to assets/src/streamer/app.js
index dc784f8..286df25 100644
--- a/assets/streamer/app.js
+++ b/assets/src/streamer/app.js
@@ -1,7 +1,7 @@
-import AdminApp from 'admin/app';
-import Model, {Set} from 'public/model';
-import Sound from 'public/sound';
-import {setEcoInterval} from 'public/utils';
+import AdminApp from '../admin';
+import Model from '../model';
+import Sound from '../sound';
+import {setEcoInterval} from '../utils';
import {Streamer, Queue} from './controllers';
diff --git a/assets/streamer/index.js b/assets/src/streamer/index.js
similarity index 71%
rename from assets/streamer/index.js
rename to assets/src/streamer/index.js
index 600156a..47df7ec 100644
--- a/assets/streamer/index.js
+++ b/assets/src/streamer/index.js
@@ -1,16 +1,19 @@
-import AdminApp from 'admin/app';
-import Model, {Set} from 'public/model';
-import Sound from 'public/sound';
-import {setEcoInterval} from 'public/utils';
+
+
+
+
+
+
diff --git a/assets/public/utils.js b/assets/src/utils.js
similarity index 66%
rename from assets/public/utils.js
rename to assets/src/utils.js
index 12d972b..4c9da99 100644
--- a/assets/public/utils.js
+++ b/assets/src/utils.js
@@ -1,12 +1,15 @@
-
-
+/**
+ * Run function with provided args only if document is not hidden
+ */
export function setEcoTimeout(func, ...args) {
return setTimeout((...args) => {
!document.hidden && func(...args)
}, ...args)
}
-
+/**
+ * Run function at specific interval only if document is not hidden
+ */
export function setEcoInterval(func, ...args) {
return setInterval((...args) => {
!document.hidden && func(...args)
diff --git a/assets/vue.config.js b/assets/vue.config.js
new file mode 100644
index 0000000..8052001
--- /dev/null
+++ b/assets/vue.config.js
@@ -0,0 +1,22 @@
+const path = require('path');
+
+const { defineConfig } = require('@vue/cli-service')
+module.exports = defineConfig({
+ transpileDependencies: true,
+ outputDir: path.resolve('../aircox/static/aircox'),
+ publicPath: './',
+ runtimeCompiler: true,
+ filenameHashing: false,
+
+ css: {
+ extract: true,
+ loaderOptions: {
+ sass: { sourceMap: true },
+ }
+ },
+
+ pages: {
+ core: { entry: 'src/core.js', },
+ admin: { entry: 'src/admin.js' },
+ }
+})
diff --git a/package.json b/package.json
index d3644be..2f1219a 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,6 @@
"license": "AGPL",
"devDependencies": {
"@fortawesome/fontawesome-free": "^6.0.0",
- "buefy": "^0.9.19",
"bulma": "^0.9.3",
"css-loader": "^6.7.1",
"file-loader": "^6.2.0",
@@ -23,6 +22,7 @@
"webpack-cli": "^4.9.2"
},
"dependencies": {
+ "@vue/cli": "^5.0.3",
"vue": "^3.2.31"
},
"scripts": {
diff --git a/webpack.config.js b/webpack.config.js
index e8224b0..96b241a 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -6,6 +6,11 @@ const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { VueLoaderPlugin } = require('vue-loader');
+new webpack.DefinePlugin({
+ __VUE_OPTIONS_API__: true,
+ __VUE_PROD_DEVTOOLS__: false,
+})
+
module.exports = (env, argv) => Object({
context: __dirname,
entry: {