Compare commits

..

1 Commits

Author SHA1 Message Date
Kevin Thomas
3ea03c92db Add warning message 2022-04-17 16:12:35 -07:00
37 changed files with 8818 additions and 20014 deletions

View File

@@ -8,7 +8,7 @@ module.exports = {
'@vue/standard' '@vue/standard'
], ],
parserOptions: { parserOptions: {
parser: '@babel/eslint-parser' parser: 'babel-eslint'
}, },
rules: { rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',

1
.github/FUNDING.yml vendored
View File

@@ -1,4 +1,3 @@
# These are supported funding model platforms # These are supported funding model platforms
github: kaythomas0 github: kaythomas0
custom: "https://kaythomas.dev/cryptocurrency.html"

View File

@@ -1,8 +0,0 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "daily"
# Disable version updates for npm dependencies
open-pull-requests-limit: 0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -1,38 +0,0 @@
name: Docker Image CI
on:
pull_request:
types:
- labeled
branches:
- 'main'
jobs:
buildx:
if: ${{ github.event.label.name == 'run-workflow' }}
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v3
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
-
name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
context: .
platforms: linux/amd64,linux/arm/v7,linux/arm64
push: true
tags: noisedash/noisedash:latest,noisedash/noisedash:${{ github.head_ref }}

View File

@@ -1,6 +1,4 @@
## Project setup ## Project setup
Requires [Node](https://nodejs.org/en/download/) and [Vue CLI](https://cli.vuejs.org/guide/installation.html)
``` ```
npm install npm install
``` ```
@@ -25,22 +23,5 @@ npm run build
npm run lint npm run lint
``` ```
### Directory Summary
Here are some of the more important files and directories:
* `config/default.json`: Contains the default configuration file
* `server/*`: Where all of the node server related code is
* `server/app.js`: The main server file where server settings are set
* `server/db.js`: Where the database is created
* `server/logger.js`: Where the logger is created and configured
* `server/bin/www.js`: The entry point of the server application (what you run to start the server)
* `server/boot/*`: These are run on server startup
* `server/routes/*`: Where all of the server routes and logic are defined
* `src/*`: Contains all the frontend code
* `src/components/*`: Where all of the Vue components are defined, split into vue and js files for each component
* `src/router/index.js`: Where all the routing and route-protection logic is defined
* `src/views/*`: Contains all the views
### Customize configuration ### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/). See [Configuration Reference](https://cli.vuejs.org/config/).

View File

@@ -1,8 +1,8 @@
FROM node:20 FROM node:14
LABEL maintainer="kaythomas@pm.me" LABEL maintainer="kaythomas@pm.me"
WORKDIR /var/noisedash WORKDIR /var/noisedash
COPY package*.json ./ COPY package*.json ./
RUN npm install --force RUN npm install
COPY . . COPY . .
ENV NODE_ENV production ENV NODE_ENV production
RUN npm run build RUN npm run build

View File

@@ -2,16 +2,16 @@
Self-hostable web tool for generating ambient noises Self-hostable web tool for generating ambient noises
![Noisedash](https://raw.githubusercontent.com/kaythomas0/noisedash/dev/.github/noisedash-screenshot-1.jpg) ![Noisedash](./.github/noisedash-screenshot-1.jpg)
(More screenshots on the [wiki](https://github.com/kaythomas0/noisedash/wiki/Screenshots)) (More screenshots on the [wiki](https://github.com/kaythomas0/noisedash/wiki/Screenshots))
# Features # Features
* Generate and customize ambient noises and user-uploadable samples (leveraging [Tone.js](https://github.com/Tonejs/Tone.js/)) * Generate and customize ambient noises and user-uploadable samples (leveraging [Tone.js](https://github.com/Tonejs/Tone.js/))
* Save "noise profiles" so you can easily switch between your created soundscapes. Import and export them for easy sharing, record them for use elsewhere * Save "noise profiles" so you can easily switch between your created soundscapes
* Fine-tune your noises with audio processing tools like filters, LFOs, and effects * Fine-tune your noises with audio processing tools like filters, LFOs, and effects
* Upload and edit audio samples (e.g rain, wind, thunder) to combine with your generated noises. Add effects to them and set playback modes * Upload audio samples (e.g rain, wind, thunder) to combine with your generated noises
* Use admin tools to manage multiple users * Use admin tools to manage multiple users
* Mobile friendly * Mobile friendly
@@ -23,9 +23,6 @@ Requires docker and docker-compose
* Download the provided [docker-compose.yml file](https://github.com/kaythomas0/noisedash/blob/main/docker-compose.yml) * Download the provided [docker-compose.yml file](https://github.com/kaythomas0/noisedash/blob/main/docker-compose.yml)
* In the same directory as the docker-compose file, created a folder called `config`, and inside it, put the provided [config file](https://github.com/kaythomas0/noisedash/blob/main/config/default.json) * In the same directory as the docker-compose file, created a folder called `config`, and inside it, put the provided [config file](https://github.com/kaythomas0/noisedash/blob/main/config/default.json)
* `maxSampleSize` is in bytes - 10GB by default
* Keep `tls` as `false` if using an external web server like nginx
* `production.json` exists in the source code and is left empty intentionally for the reason outlined here: https://github.com/node-config/node-config/wiki/Strict-Mode#node_env-value-of-node_env-did-not-match-any-deployment-config-file-names=
* Edit the config file to your preference * Edit the config file to your preference
* Bring the container up: * Bring the container up:
@@ -37,26 +34,9 @@ docker-compose up -d
(Raspberry Pi compatible images are available, see armv7 images on [Docker Hub](https://hub.docker.com/repository/docker/noisedash/noisedash)) (Raspberry Pi compatible images are available, see armv7 images on [Docker Hub](https://hub.docker.com/repository/docker/noisedash/noisedash))
## Kubernetes
You can apply the manifest.yaml in the kubernetes folder to install Noisedash into your Kubernetes cluster.
Optionally, uncomment the last lines in the file to also create an ingress. The ingress, commented out by default, needs to have the clusterIssuser annotation set to your cluster issuer (default: letsencrypt-prod) and the ingress class set to your Ingress class (default: Nginx)
``` bash
$ kubectl apply -f ./kubernetes/manifest.yaml
persistentvolumeclaim/db-pvc created
persistentvolumeclaim/samples-pvc created
deployment.apps/noisedash created
service/noisedash created
configmap/noisedashcfg created
ingress.networking.k8s.io/noisedashingress created
```
## From Source ## From Source
Requires node 20 and npm Requires node 14 and npm
* Clone the repo: * Clone the repo:
@@ -85,21 +65,3 @@ npm run server-prod
# Contributing # Contributing
See [CONTRIBUTING.md](https://github.com/kaythomas0/noisedash/blob/main/CONTRIBUTING.md) See [CONTRIBUTING.md](https://github.com/kaythomas0/noisedash/blob/main/CONTRIBUTING.md)
# License
Noisedash, a self-hostable web tool for generating ambient noises
Copyright (C) 2021 Kay Thomas <kaythomas@pm.me>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.

View File

@@ -3,9 +3,9 @@
"listeningPort": 1432, "listeningPort": 1432,
"sessionFileStorePath": "sessions", "sessionFileStorePath": "sessions",
"sampleUploadPath": "samples", "sampleUploadPath": "samples",
"maxSampleSize": 10737418240, "maxSampleSize": 10737418240, // In bytes, 10GB by default
"logFile": "log/noisedash.log", "logFile": "log/noisedash.log",
"tls": false, "tls": false, // Keep this as false if using an external web server like nginx
"tlsKey": "certs/key.pem", "tlsKey": "certs/key.pem",
"tlsCert": "certs/cert.pem" "tlsCert": "certs/cert.pem"
} }

View File

@@ -1 +1 @@
{} {} // Left empty intentionally: https://github.com/node-config/node-config/wiki/Strict-Mode#node_env-value-of-node_env-did-not-match-any-deployment-config-file-names=

View File

@@ -1,240 +0,0 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: db-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: samples-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: noisedash
spec:
replicas: 1
selector:
matchLabels:
app: noisedash
template:
metadata:
labels:
app: noisedash
spec:
containers:
- name: noisedash
image: noisedash/noisedash:latest
ports:
- containerPort: 1432
volumeMounts:
- name: db
mountPath: /var/noisedash/db
- name: samples
mountPath: /var/noisedash/samples
- name: config
mountPath: /var/noisedash/config/default.json
subPath: config.json
volumes:
- name: db
persistentVolumeClaim:
claimName: db-pvc
- name: samples
persistentVolumeClaim:
claimName: samples-pvc
- name: config
configMap:
name: noisedashcfg
---
apiVersion: v1
kind: Service
metadata:
name: noisedash
spec:
selector:
app: noisedash
ports:
- protocol: TCP
port: 80
targetPort: 1432
---
apiVersion: v1
kind: ConfigMap
metadata:
name: noisedashcfg
data:
config.json: |
{
"Server": {
"listeningPort": 1432,
"sessionFileStorePath": "sessions",
"sampleUploadPath": "samples",
"maxSampleSize": 10737418240,
"logFile": "log/noisedash.log",
"tls": false,
"tlsKey": "certs/key.pem",
"tlsCert": "certs/cert.pem"
}
}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
kubernetes.io/ingress.class: nginx
kubernetes.io/tls-acme: "true"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
labels:
app.kubernetes.io/instance: noisedash
name: noisedashingress
spec:
rules:
- host: noisedash.freshbrewed.science
http:
paths:
- backend:
service:
name: noisedash
port:
number: 80
path: /
pathType: ImplementationSpecific
tls:
- hosts:
- noisedash.freshbrewed.science
secretName: noisedash-tls
builder@DESKTOP-QADGF36:~/Workspaces/pyplanereport$
builder@DESKTOP-QADGF36:~/Workspaces/pyplanereport$ cat noiseall.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: db-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: samples-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: noisedash
spec:
replicas: 1
selector:
matchLabels:
app: noisedash
template:
metadata:
labels:
app: noisedash
spec:
containers:
- name: noisedash
image: noisedash/noisedash:latest
ports:
- containerPort: 1432
volumeMounts:
- name: db
mountPath: /var/noisedash/db
- name: samples
mountPath: /var/noisedash/samples
- name: config
mountPath: /var/noisedash/config/default.json
subPath: config.json
volumes:
- name: db
persistentVolumeClaim:
claimName: db-pvc
- name: samples
persistentVolumeClaim:
claimName: samples-pvc
- name: config
configMap:
name: noisedashcfg
---
apiVersion: v1
kind: Service
metadata:
name: noisedash
spec:
selector:
app: noisedash
ports:
- protocol: TCP
port: 80
targetPort: 1432
---
apiVersion: v1
kind: ConfigMap
metadata:
name: noisedashcfg
data:
config.json: |
{
"Server": {
"listeningPort": 1432,
"sessionFileStorePath": "sessions",
"sampleUploadPath": "samples",
"maxSampleSize": 10737418240,
"logFile": "log/noisedash.log",
"tls": false,
"tlsKey": "certs/key.pem",
"tlsCert": "certs/cert.pem"
}
}
# ---
# apiVersion: networking.k8s.io/v1
# kind: Ingress
# metadata:
# annotations:
# cert-manager.io/cluster-issuer: letsencrypt-prod
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
# nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
# nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
# labels:
# app.kubernetes.io/instance: noisedash
# name: noisedashingress
# spec:
# rules:
# - host: noisedash.freshbrewed.science
# http:
# paths:
# - backend:
# service:
# name: noisedash
# port:
# number: 80
# path: /
# pathType: ImplementationSpecific
# tls:
# - hosts:
# - noisedash.freshbrewed.science
# secretName: noisedash-tls

27398
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,58 +1,57 @@
{ {
"name": "noisedash", "name": "noisedash",
"version": "0.7.0", "descriptions": "Self-hostable web tool for generating ambient noises",
"private": true, "homepage": "https://github.com/kaythomas0/noisedash",
"bugs": "https://github.com/kaythomas0/noisedash/issues",
"license": "AGPL-3.0-or-later",
"author": "Kay Thomas <kaythomas@pm.me> (https://kaythomas.dev)", "author": "Kay Thomas <kaythomas@pm.me> (https://kaythomas.dev)",
"version": "0.2.0",
"private": true,
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"server": "node server/bin/www.js", "server": "node server/bin/www.js",
"server-prod": "NODE_ENV=production node server/bin/www.js" "server-prod": "NODE_ENV=production node server/bin/www.js",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
}, },
"dependencies": { "dependencies": {
"@vscode/sqlite3": "^5.0.8", "@vscode/sqlite3": "^5.0.8",
"axios": "^1.6.0", "axios": "^0.21.4",
"config": "^3.3.6", "config": "^3.3.6",
"connect-history-api-fallback": "^1.6.0", "connect-history-api-fallback": "^1.6.0",
"cookie-parser": "^1.4.5", "cookie-parser": "^1.4.5",
"core-js": "^3.23.5", "core-js": "^3.19.1",
"express": "^4.18.1", "express": "^4.17.1",
"express-session": "^1.17.3", "express-session": "^1.17.2",
"multer": "^1.4.5-lts.1", "multer": "^1.4.3",
"passport": "^0.6.0", "passport": "^0.4.1",
"passport-local": "^1.0.0", "passport-local": "^1.0.0",
"path": "^0.12.7", "path": "^0.12.7",
"session-file-store": "^1.5.0", "session-file-store": "^1.5.0",
"tone": "^14.7.77", "tone": "^14.7.77",
"vue": "^2.7.16", "vue": "^2.6.11",
"vue-router": "^3.5.4", "vue-router": "^3.5.3",
"vuetify": "^2.6.10", "vuetify": "^2.5.12",
"winston": "^3.3.3" "winston": "^3.3.3"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.12.16", "@vue/cli-plugin-babel": "^4.5.15",
"@babel/eslint-parser": "^7.12.16", "@vue/cli-plugin-eslint": "^4.5.15",
"@vue/cli-plugin-babel": "^5.0.8", "@vue/cli-plugin-router": "^4.5.15",
"@vue/cli-plugin-eslint": "^5.0.8", "@vue/cli-service": "^4.5.15",
"@vue/cli-plugin-router": "^5.0.8", "@vue/eslint-config-standard": "^5.1.2",
"@vue/cli-service": "^5.0.8", "babel-eslint": "^10.1.0",
"@vue/eslint-config-standard": "^6.1.0", "eslint": "^6.7.2",
"eslint": "^7.32.0",
"eslint-plugin-html": "^6.2.0", "eslint-plugin-html": "^6.2.0",
"eslint-plugin-import": "^2.25.3", "eslint-plugin-import": "^2.25.2",
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0", "eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.0", "eslint-plugin-standard": "^4.0.0",
"eslint-plugin-vue": "^7.20.0", "eslint-plugin-vue": "^6.2.2",
"sass": "~1.32.0", "sass": "~1.32.0",
"sass-loader": "^10.0.0", "sass-loader": "^10.0.0",
"vue-cli-plugin-vuetify": "^2.5.8", "vue-cli-plugin-vuetify": "^2.4.3",
"vue-template-compiler": "^2.7.16", "vue-template-compiler": "^2.6.11",
"vuetify-loader": "^1.7.3" "vuetify-loader": "^1.7.3"
}, }
"bugs": "https://github.com/kaythomas0/noisedash/issues",
"descriptions": "Self-hostable web tool for generating ambient noises",
"homepage": "https://github.com/kaythomas0/noisedash",
"license": "AGPL-3.0-or-later"
} }

View File

@@ -36,8 +36,7 @@ app.use(session({
store: new FileStore(fileStoreOptions), store: new FileStore(fileStoreOptions),
secret: sessionSecret, secret: sessionSecret,
resave: true, resave: true,
saveUninitialized: true, saveUninitialized: true
cookie: { sameSite: 'strict' }
})) }))
app.use((req, res, next) => { app.use((req, res, next) => {
const msgs = req.session.messages || [] const msgs = req.session.messages || []

View File

@@ -68,4 +68,5 @@ function onListening () {
? 'pipe ' + addr ? 'pipe ' + addr
: 'port ' + addr.port : 'port ' + addr.port
logger.log('info', 'Listening on %s', bind) logger.log('info', 'Listening on %s', bind)
logger.log('info', 'WARNING, IMPORTANT: It looks like you are using the kevinthomas0/noisedash docker image. Please update your image to noisedash/noisedash. The kevinthomas0/noisedash image will no longer receive updates.')
} }

View File

@@ -59,8 +59,7 @@ module.exports = function () {
} else { } else {
const userVersion = row.user_version const userVersion = row.user_version
db.serialize(() => { if (userVersion === 0) {
if (userVersion < 1) {
db.run('ALTER TABLE samples ADD COLUMN fade_in REAL DEFAULT 0') db.run('ALTER TABLE samples ADD COLUMN fade_in REAL DEFAULT 0')
db.run('ALTER TABLE samples ADD COLUMN loop_points_enabled INTEGER DEFAULT 0') db.run('ALTER TABLE samples ADD COLUMN loop_points_enabled INTEGER DEFAULT 0')
db.run('ALTER TABLE samples ADD COLUMN loop_start REAL DEFAULT 0') db.run('ALTER TABLE samples ADD COLUMN loop_start REAL DEFAULT 0')
@@ -68,37 +67,6 @@ module.exports = function () {
db.run('PRAGMA user_version = 1') db.run('PRAGMA user_version = 1')
} }
if (userVersion < 2) {
db.run('ALTER TABLE users ADD COLUMN preferences TEXT DEFAULT "{}"')
db.run('PRAGMA user_version = 2')
}
if (userVersion < 3) {
db.run('ALTER TABLE profiles_samples ADD COLUMN reverb_enabled INTEGER DEFAULT 0')
db.run('ALTER TABLE profiles_samples ADD COLUMN reverb_pre_delay REAL DEFAULT 0')
db.run('ALTER TABLE profiles_samples ADD COLUMN reverb_decay REAL DEFAULT 0')
db.run('ALTER TABLE profiles_samples ADD COLUMN reverb_wet INTEGER DEFAULT 0')
db.run('ALTER TABLE profiles_samples ADD COLUMN playback_mode TEXT DEFAULT "continuous"')
db.run('ALTER TABLE profiles_samples ADD COLUMN sporadic_min INTEGER DEFAULT 30')
db.run('ALTER TABLE profiles_samples ADD COLUMN sporadic_max INTEGER DEFAULT 300')
db.run('PRAGMA user_version = 3')
}
if (userVersion < 4) {
db.run('UPDATE users SET preferences = ? WHERE preferences = ?',
['{"accentColor":{"alpha":1,"hex":"#607D8B","hexa":"#607D8BFF","hsla":{"h":200,"s":18,"l":46,"a":1},"hsva":{"h":200,"s":31,"v":55,"a":1},"hue":200,"rgba":{"r":96,"g":125,"b":139,"a":1}}}', '{}'],
(err) => {
if (err) {
logger.error(err)
} else {
db.run('PRAGMA user_version = 4')
}
})
}
})
} }
}) })
}) })

View File

@@ -36,14 +36,8 @@ router.get('/admin', (req, res) => {
}) })
router.get('/logout', (req, res) => { router.get('/logout', (req, res) => {
req.logout((err) => { req.logout()
if (err) {
logger.error(err)
res.sendStatus(500)
} else {
res.sendStatus(200) res.sendStatus(200)
}
})
}) })
router.get('/setup', (req, res) => { router.get('/setup', (req, res) => {

View File

@@ -59,28 +59,10 @@ router.post('/profiles', (req, res) => {
profileID = this.lastID profileID = this.lastID
req.body.samples.forEach(s => { req.body.samples.forEach(s => {
db.run(`INSERT INTO profiles_samples( db.run('INSERT INTO profiles_samples (profile, sample, volume) VALUES (?, ?, ?)', [
profile,
sample,
volume,
reverb_enabled,
reverb_pre_delay,
reverb_decay,
reverb_wet,
playback_mode,
sporadic_min,
sporadic_max)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
profileID, profileID,
s.id, s.id,
s.volume, s.volume
s.reverbEnabled,
s.reverbPreDelay,
s.reverbDecay,
s.reverbWet,
s.playbackMode,
s.sporadicMin,
s.sporadicMax
], ],
(err) => { (err) => {
if (err) { if (err) {
@@ -222,28 +204,10 @@ router.put('/profiles/:profileId', (req, res) => {
}) })
req.body.samples.forEach(s => { req.body.samples.forEach(s => {
db.run(`INSERT INTO profiles_samples( db.run('INSERT INTO profiles_samples (profile, sample, volume) VALUES (?, ?, ?)', [
profile,
sample,
volume,
reverb_enabled,
reverb_pre_delay,
reverb_decay,
reverb_wet,
playback_mode,
sporadic_min,
sporadic_max)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
req.params.profileId, req.params.profileId,
s.id, s.id,
s.volume, s.volume
s.reverbEnabled,
s.reverbPreDelay,
s.reverbDecay,
s.reverbWet,
s.playbackMode,
s.sporadicMin,
s.sporadicMax
], ],
(err) => { (err) => {
if (err) { if (err) {
@@ -391,13 +355,6 @@ router.get('/profiles/:profileId', (req, res) => {
samples.id, samples.id,
name, name,
profiles_samples.volume, profiles_samples.volume,
profiles_samples.reverb_enabled as reverbEnabled,
profiles_samples.reverb_pre_delay as reverbPreDelay,
profiles_samples.reverb_decay as reverbDecay,
profiles_samples.reverb_wet as reverbWet,
profiles_samples.playback_mode as playbackMode,
profiles_samples.sporadic_min as sporadicMin,
profiles_samples.sporadic_max as sporadicMax,
fade_in as fadeIn, fade_in as fadeIn,
loop_points_enabled as loopPointsEnabled, loop_points_enabled as loopPointsEnabled,
loop_start as loopStart, loop_start as loopStart,
@@ -425,13 +382,6 @@ router.get('/profiles/:profileId', (req, res) => {
sample.loopPointsEnabled = row.loopPointsEnabled === 1 sample.loopPointsEnabled = row.loopPointsEnabled === 1
sample.loopStart = row.loopStart sample.loopStart = row.loopStart
sample.loopEnd = row.loopEnd sample.loopEnd = row.loopEnd
sample.reverbEnabled = row.reverbEnabled === 1
sample.reverbPreDelay = row.reverbPreDelay
sample.reverbDecay = row.reverbDecay
sample.reverbWet = row.reverbWet
sample.playbackMode = row.playbackMode
sample.sporadicMin = row.sporadicMin
sample.sporadicMax = row.sporadicMax
samples.push(sample) samples.push(sample)
}) })

View File

@@ -9,11 +9,7 @@ router.get('/users/current', (req, res) => {
return res.sendStatus(401) return res.sendStatus(401)
} }
db.get(`SELECT db.get('SELECT is_admin as isAdmin, dark_mode as darkMode, can_upload as canUpload, * FROM users WHERE id = ?', [req.user.id], (err, row) => {
is_admin as isAdmin,
dark_mode as darkMode,
can_upload as canUpload,
* FROM users WHERE id = ?`, [req.user.id], (err, row) => {
if (err) { if (err) {
logger.error(err) logger.error(err)
return res.sendStatus(500) return res.sendStatus(500)
@@ -28,7 +24,6 @@ router.get('/users/current', (req, res) => {
user.isAdmin = row.isAdmin === 1 user.isAdmin = row.isAdmin === 1
user.darkMode = row.darkMode === 1 user.darkMode = row.darkMode === 1
user.canUpload = row.canUpload === 1 user.canUpload = row.canUpload === 1
user.preferences = JSON.parse(row.preferences)
} }
res.json({ user: user }) res.json({ user: user })
@@ -72,8 +67,6 @@ router.post('/users', (req, res) => {
return res.sendStatus(500) return res.sendStatus(500)
} }
const defaultPreferences = '{"accentColor":{"alpha":1,"hex":"#607D8B","hexa":"#607D8BFF","hsla":{"h":200,"s":18,"l":46,"a":1},"hsva":{"h":200,"s":31,"v":55,"a":1},"hue":200,"rgba":{"r":96,"g":125,"b":139,"a":1}}}'
if (row.count !== 0) { if (row.count !== 0) {
if (!req.user) { if (!req.user) {
return res.sendStatus(401) return res.sendStatus(401)
@@ -96,16 +89,15 @@ router.post('/users', (req, res) => {
return res.sendStatus(500) return res.sendStatus(500)
} }
db.run(`INSERT INTO users (username, hashed_password, salt, name, is_admin, dark_mode, can_upload, preferences) db.run(`INSERT INTO users (username, hashed_password, salt, name, is_admin, dark_mode, can_upload)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [ VALUES (?, ?, ?, ?, ?, ?, ?)`, [
req.body.username, req.body.username,
hashedPassword, hashedPassword,
salt, salt,
req.body.name, req.body.name,
req.body.isAdmin, req.body.isAdmin,
req.body.darkMode, req.body.darkMode,
req.body.canUpload, req.body.canUpload
defaultPreferences
], (err) => { ], (err) => {
if (err) { if (err) {
logger.error(err) logger.error(err)
@@ -128,16 +120,15 @@ router.post('/users', (req, res) => {
return res.sendStatus(500) return res.sendStatus(500)
} }
db.run(`INSERT INTO users (username, hashed_password, salt, name, is_admin, dark_mode, can_upload, preferences) db.run(`INSERT INTO users (username, hashed_password, salt, name, is_admin, dark_mode, can_upload)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [ VALUES (?, ?, ?, ?, ?, ?, ?)`, [
req.body.username, req.body.username,
hashedPassword, hashedPassword,
salt, salt,
req.body.name, req.body.name,
req.body.isAdmin, req.body.isAdmin,
req.body.darkMode, req.body.darkMode,
req.body.canUpload, req.body.canUpload
defaultPreferences
], function (err) { ], function (err) {
if (err) { if (err) {
logger.error(err) logger.error(err)
@@ -296,23 +287,4 @@ router.delete('/users/:userId', (req, res) => {
}) })
}) })
router.patch('/users/preferences', (req, res) => {
if (!req.user) {
return res.sendStatus(401)
}
const preferences = JSON.stringify(req.body.preferences)
db.serialize(() => {
db.run('UPDATE users SET preferences = ? WHERE id = ?', [preferences, req.user.id], (err) => {
if (err) {
logger.error(err)
return res.sendStatus(500)
} else {
return res.sendStatus(200)
}
})
})
})
module.exports = router module.exports = router

View File

@@ -8,10 +8,6 @@
</v-col> </v-col>
</v-row> </v-row>
<h2 class="headline font-weight-bold mb-3">
User Management
</h2>
<v-col cols="12"> <v-col cols="12">
<v-row> <v-row>
ID: {{ currentUser.id }} ID: {{ currentUser.id }}
@@ -28,9 +24,9 @@
v-model="changePasswordDialog" v-model="changePasswordDialog"
max-width="600px" max-width="600px"
> >
<template #activator="{ on, attrs }"> <template v-slot:activator="{ on, attrs }">
<v-btn <v-btn
class="mt-5 mb-10" class="my-3"
v-bind="attrs" v-bind="attrs"
v-on="on" v-on="on"
@click="resetChangePasswordForm" @click="resetChangePasswordForm"
@@ -81,42 +77,19 @@
</v-form> </v-form>
</v-dialog> </v-dialog>
<h2 class="headline font-weight-bold mb-3">
Preferences
</h2>
<h3 class="font-weight-bold">
Dark Mode
</h3>
<v-switch <v-switch
v-model="$vuetify.theme.dark" v-model="$vuetify.theme.dark"
:label="`${$vuetify.theme.dark ? 'On' : 'Off'}`" label="Dark Mode"
@change="toggleDarkMode" @change="toggleDarkMode"
/> />
<h3 class="mb-5 font-weight-bold">
Accent Color
</h3>
<v-color-picker
v-model="accentColor"
/>
<v-btn
class="mt-5"
@click="updateAccentColor"
>
Apply
</v-btn>
<v-snackbar <v-snackbar
v-model="snackbar" v-model="snackbar"
timeout="3000" timeout="3000"
> >
{{ snackbarText }} {{ snackbarText }}
<template #action="{ attrs }"> <template v-slot:action="{ attrs }">
<v-btn <v-btn
text text
v-bind="attrs" v-bind="attrs"

View File

@@ -12,7 +12,7 @@
v-model="registerUserDialog" v-model="registerUserDialog"
max-width="600px" max-width="600px"
> >
<template #activator="{ on, attrs }"> <template v-slot:activator="{ on, attrs }">
<v-btn <v-btn
v-bind="attrs" v-bind="attrs"
v-on="on" v-on="on"
@@ -139,7 +139,7 @@
> >
{{ snackbarText }} {{ snackbarText }}
<template #action="{ attrs }"> <template v-slot:action="{ attrs }">
<v-btn <v-btn
text text
v-bind="attrs" v-bind="attrs"

View File

@@ -7,7 +7,7 @@
dense dense
> >
<v-app-bar-nav-icon <v-app-bar-nav-icon
@click="checkForAdmin" @click="getCurrentUser"
/> />
</v-app-bar> </v-app-bar>
<v-navigation-drawer <v-navigation-drawer
@@ -63,7 +63,7 @@
</v-list-item> </v-list-item>
</v-list-item-group> </v-list-item-group>
</v-list> </v-list>
<template #append> <template v-slot:append>
<v-btn <v-btn
block block
href="https://github.com/kaythomas0/noisedash" href="https://github.com/kaythomas0/noisedash"

View File

@@ -50,7 +50,7 @@
> >
{{ snackbarText }} {{ snackbarText }}
<template #action="{ attrs }"> <template v-slot:action="{ attrs }">
<v-btn <v-btn
text text
v-bind="attrs" v-bind="attrs"

View File

@@ -14,7 +14,7 @@
<v-row justify="center"> <v-row justify="center">
<v-btn <v-btn
:disabled="playDisabled || !isTimerValid || (loadedSamples.length != 0 && !isSporadicValid)" :disabled="playDisabled || !isTimerValid"
class="mx-3 mb-5" class="mx-3 mb-5"
fab fab
large large
@@ -50,7 +50,7 @@
label="Profiles" label="Profiles"
class="mx-3 mb-5" class="mx-3 mb-5"
:disabled="playDisabled" :disabled="playDisabled"
@change="loadProfile(true)" @change="loadProfile"
/> />
</v-row> </v-row>
@@ -66,14 +66,14 @@
class="mx-3 my-3" class="mx-3 my-3"
@click="updateProfile" @click="updateProfile"
> >
{{ unsavedWork ? 'Save Profile*' : 'Save Profile' }} Save Profile
</v-btn> </v-btn>
<v-dialog <v-dialog
v-model="profileDialog" v-model="profileDialog"
max-width="600px" max-width="600px"
> >
<template #activator="{ on, attrs }"> <template v-slot:activator="{ on, attrs }">
<v-btn <v-btn
v-bind="attrs" v-bind="attrs"
class="mx-3 my-3" class="mx-3 my-3"
@@ -90,16 +90,18 @@
> >
<v-card> <v-card>
<v-card-title> <v-card-title>
<span class="text-h5">Save Profile As...</span> <span class="text-h5">Profile Name</span>
</v-card-title> </v-card-title>
<v-card-text> <v-card-text>
<v-container> <v-container>
<v-row> <v-row>
<v-col cols="12">
<v-text-field <v-text-field
v-model="profileName" v-model="profileName"
label="Profile Name" label="Profile Name"
:rules="[rules.required()]" :rules="[rules.required()]"
/> />
</v-col>
</v-row> </v-row>
</v-container> </v-container>
</v-card-text> </v-card-text>
@@ -127,7 +129,7 @@
v-model="profileMoreDialog" v-model="profileMoreDialog"
max-width="600px" max-width="600px"
> >
<template #activator="{ on, attrs }"> <template v-slot:activator="{ on, attrs }">
<v-btn <v-btn
v-bind="attrs" v-bind="attrs"
class="mx-3 my-3" class="mx-3 my-3"
@@ -166,16 +168,6 @@
Export Profile Export Profile
</v-list-item-title> </v-list-item-title>
</v-list-item> </v-list-item>
<v-list-item
@click="openStartRecordingDialog"
>
<v-list-item-icon>
<v-icon>mdi-record</v-icon>
</v-list-item-icon>
<v-list-item-title>
Record Profile Audio
</v-list-item-title>
</v-list-item>
</v-list-item-group> </v-list-item-group>
</v-col> </v-col>
</v-row> </v-row>
@@ -260,6 +252,7 @@
:items="profileItems" :items="profileItems"
return-object return-object
label="Profiles" label="Profiles"
class="mx-3 mb-5"
/> />
</v-row> </v-row>
</v-container> </v-container>
@@ -281,120 +274,6 @@
</v-card-actions> </v-card-actions>
</v-card> </v-card>
</v-dialog> </v-dialog>
<v-dialog
v-model="startRecordingDialog"
max-width="600px"
>
<v-form
ref="startRecordingForm"
v-model="isRecordingValid"
>
<v-card>
<v-card-title>
<span class="text-h5">Start Recording</span>
</v-card-title>
<v-card-text>
<v-container>
<v-row>
<p>Select profile to record audio for. This is only supported on Chrome and Firefox. Current profile will be saved before recording.</p>
</v-row>
<v-row>
<v-select
v-model="recordedProfile"
:items="profileItems"
return-object
label="Profiles"
/>
</v-row>
<v-row>
<v-text-field
v-model="recordingFileName"
label="File Name"
:rules="[rules.required()]"
/>
</v-row>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
text
@click="startRecordingDialog = false"
>
Close
</v-btn>
<v-btn
text
:disabled="!isRecordingValid"
@click="startRecording"
>
Record
</v-btn>
</v-card-actions>
</v-card>
</v-form>
</v-dialog>
<v-dialog
v-model="recordingDialog"
max-width="600px"
persistent
>
<v-card>
<v-card-title>
<span class="text-h5">Recording</span>
</v-card-title>
<v-card-text>
Time Elapsed: {{ recordingTimeElapsed }} Seconds
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
text
@click="cancelRecording"
>
Cancel
</v-btn>
<v-btn
text
@click="stopRecording"
>
Stop and Save
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog
v-model="confirmSwitchProfileDialog"
max-width="600px"
persistent
>
<v-card>
<v-card-title>
<span class="text-h5">Save Profile?</span>
</v-card-title>
<v-card-text>
You have unsaved work on your current profile. Would you like to save it before switching?
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
text
@click="discardChanges"
>
Discard Changes
</v-btn>
<v-btn
text
@click="saveChanges"
>
Save Profile
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-col> </v-col>
<v-col cols="12"> <v-col cols="12">
@@ -466,7 +345,7 @@
label="Volume" label="Volume"
thumb-label thumb-label
max="0" max="0"
min="-60" min="-30"
class="mx-3" class="mx-3"
@input="updateVolume" @input="updateVolume"
/> />
@@ -652,15 +531,10 @@
<v-row <v-row
justify="center" justify="center"
> >
<v-col />
<v-col>
<h2 class="mb-5">
{{ sample.name }} {{ sample.name }}
</h2> </v-row>
</v-col>
<v-col> <v-row>
<v-btn <v-btn
icon icon
:disabled="playDisabled" :disabled="playDisabled"
@@ -668,16 +542,13 @@
> >
<v-icon>mdi-delete</v-icon> <v-icon>mdi-delete</v-icon>
</v-btn> </v-btn>
</v-col>
</v-row>
<v-row>
<v-slider <v-slider
v-model="sample.volume" v-model="sample.volume"
label="Volume" label="Volume"
thumb-label thumb-label
max="0" max="0"
min="-60" min="-30"
class="mx-3" class="mx-3"
@input="updateSampleVolume(sample.id, index)" @input="updateSampleVolume(sample.id, index)"
/> />
@@ -687,142 +558,6 @@
<p>{{ loadedSamples[index].volume }}</p> <p>{{ loadedSamples[index].volume }}</p>
</div> </div>
</v-row> </v-row>
<v-row
justify="center"
>
<h3 class="font-weight-regular mb-9">
Effects
</h3>
</v-row>
<v-expansion-panels class="mb-9">
<v-expansion-panel>
<v-expansion-panel-header>
Reverb
</v-expansion-panel-header>
<v-expansion-panel-content>
<v-row justify="center">
<v-checkbox
v-model="sample.reverbEnabled"
:disabled="playDisabled"
label="Enabled"
/>
</v-row>
<v-row justify="center">
<v-slider
v-model="sample.reverbPreDelay"
:disabled="playDisabled || !sample.reverbEnabled"
label="Pre Delay"
thumb-label
max="16"
min="0"
step="0.5"
class="mx-3"
/>
<div
class="mx-3"
>
<p>{{ sample.reverbPreDelay }}</p>
</div>
</v-row>
<v-row justify="center">
<v-slider
v-model="sample.reverbDecay"
:disabled="playDisabled || !sample.reverbEnabled"
label="Decay"
thumb-label
max="16"
min="0"
step="0.5"
class="mx-3"
@input="updateVolume"
/>
<div
class="mx-3"
>
<p> {{ sample.reverbDecay }}</p>
</div>
</v-row>
<v-row justify="center">
<v-slider
v-model="sample.reverbWet"
:disabled="playDisabled || !sample.reverbEnabled"
label="Wet"
thumb-label
max="1"
min="0"
step="0.01"
class="mx-3"
@input="updateVolume"
/>
<div
class="mx-3"
>
<p>{{ sample.reverbWet }}%</p>
</div>
</v-row>
</v-expansion-panel-content>
</v-expansion-panel>
</v-expansion-panels>
<v-row justify="center">
<h3 class="font-weight-regular">
Playback Mode
</h3>
</v-row>
<v-row
class="mb-5"
>
<v-radio-group
v-model="sample.playbackMode"
:disabled="playDisabled"
mandatory
>
<v-radio
label="Continuous (Looped)"
value="continuous"
/>
<v-radio
label="Sporadic (Not Looped, Plays Randomly Within Interval)"
value="sporadic"
/>
</v-radio-group>
</v-row>
<v-form
v-model="isSporadicValid"
>
<v-row
justify="center"
>
<v-text-field
v-model="sample.sporadicMin"
type="number"
label="Sporadic Min"
class="mx-3"
:disabled="sample.playbackMode != 'sporadic' || playDisabled"
:rules="[rules.gt(-1)]"
/>
<v-text-field
v-model="sample.sporadicMax"
type="number"
label="Sporadic Max"
class="mx-3"
:disabled="sample.playbackMode != 'sporadic' || playDisabled"
:rules="[rules.gt(0)]"
/>
</v-row>
</v-form>
<v-divider
class="mt-7"
/>
</v-container> </v-container>
</v-row> </v-row>
@@ -830,7 +565,7 @@
v-model="addSampleDialog" v-model="addSampleDialog"
max-width="600px" max-width="600px"
> >
<template #activator="{ on, attrs }"> <template v-slot:activator="{ on, attrs }">
<v-btn <v-btn
v-bind="attrs" v-bind="attrs"
class="mx-3 my-3 mb-5" class="mx-3 my-3 mb-5"
@@ -890,7 +625,7 @@
v-model="uploadSampleDialog" v-model="uploadSampleDialog"
max-width="600px" max-width="600px"
> >
<template #activator="{ on, attrs }"> <template v-slot:activator="{ on, attrs }">
<v-btn <v-btn
v-bind="attrs" v-bind="attrs"
class="mx-3 my-3 mb-5" class="mx-3 my-3 mb-5"
@@ -911,7 +646,9 @@
<v-card-text> <v-card-text>
<v-container> <v-container>
<v-row> <v-row>
<v-col cols="12">
<p><strong>WARNING:</strong> Uploaded samples are publicly accessible.</p> <p><strong>WARNING:</strong> Uploaded samples are publicly accessible.</p>
</v-col>
</v-row> </v-row>
<v-row> <v-row>
<v-file-input <v-file-input
@@ -922,11 +659,13 @@
/> />
</v-row> </v-row>
<v-row> <v-row>
<v-col cols="12">
<v-text-field <v-text-field
v-model="sampleName" v-model="sampleName"
label="Sample Name" label="Sample Name"
:rules="[rules.required()]" :rules="[rules.required()]"
/> />
</v-col>
</v-row> </v-row>
</v-container> </v-container>
</v-card-text> </v-card-text>
@@ -954,7 +693,7 @@
v-model="editSampleDialog" v-model="editSampleDialog"
max-width="600px" max-width="600px"
> >
<template #activator="{ on, attrs }"> <template v-slot:activator="{ on, attrs }">
<v-btn <v-btn
v-bind="attrs" v-bind="attrs"
class="mx-3 my-3 mb-5" class="mx-3 my-3 mb-5"
@@ -972,7 +711,7 @@
> >
<v-card> <v-card>
<v-card-title> <v-card-title>
<span class="text-h5">Edit Samples</span> <span class="text-h5">Edit Sample</span>
</v-card-title> </v-card-title>
<v-card-text> <v-card-text>
<v-container> <v-container>
@@ -983,6 +722,7 @@
item-text="name" item-text="name"
return-object return-object
label="Samples" label="Samples"
class="mx-3"
@change="loadPreviewSample" @change="loadPreviewSample"
/> />
</v-row> </v-row>
@@ -995,8 +735,8 @@
<v-checkbox <v-checkbox
v-model="previewSampleLoopPointsEnabled" v-model="previewSampleLoopPointsEnabled"
:disabled="previewSamplePlaying" :disabled="previewSamplePlaying"
label="Use Loop Points (Continuous Playback Mode Only)" label="Use Loop Points"
@change="updatePreviewSampleLoopPoints" class="mx-3"
/> />
</v-row> </v-row>
@@ -1005,7 +745,7 @@
v-model="previewSampleLoopStart" v-model="previewSampleLoopStart"
type="number" type="number"
label="Loop Start Time" label="Loop Start Time"
class="mr-3" class="mx-3"
:disabled="!previewSampleLoopPointsEnabled || previewSamplePlaying" :disabled="!previewSampleLoopPointsEnabled || previewSamplePlaying"
:rules="[rules.gt(-1)]" :rules="[rules.gt(-1)]"
@change="updatePreviewSamplePlayerLoopPoints" @change="updatePreviewSamplePlayerLoopPoints"
@@ -1015,7 +755,7 @@
v-model="previewSampleLoopEnd" v-model="previewSampleLoopEnd"
type="number" type="number"
label="Loop End Time" label="Loop End Time"
class="ml-3" class="mx-3"
:disabled="!previewSampleLoopPointsEnabled || previewSamplePlaying" :disabled="!previewSampleLoopPointsEnabled || previewSamplePlaying"
:rules="[rules.gt(-1), rules.lt(previewSampleLength)]" :rules="[rules.gt(-1), rules.lt(previewSampleLength)]"
@change="updatePreviewSamplePlayerLoopPoints" @change="updatePreviewSamplePlayerLoopPoints"
@@ -1026,7 +766,8 @@
<v-text-field <v-text-field
v-model="previewSampleFadeIn" v-model="previewSampleFadeIn"
type="number" type="number"
label="Fade In Time (In continuous mode, this only effects first playback)" label="Fade In Time"
class="mx-3"
:disabled="previewSamplePlaying" :disabled="previewSamplePlaying"
:rules="[rules.gt(-1)]" :rules="[rules.gt(-1)]"
@change="updatePreviewSamplePlayerFadeIn" @change="updatePreviewSamplePlayerFadeIn"
@@ -1071,7 +812,7 @@
> >
{{ infoSnackbarText }} {{ infoSnackbarText }}
<template #action="{ attrs }"> <template v-slot:action="{ attrs }">
<v-btn <v-btn
text text
v-bind="attrs" v-bind="attrs"
@@ -1089,7 +830,7 @@
> >
{{ errorSnackbarText }} {{ errorSnackbarText }}
<template #action="{ attrs }"> <template v-slot:action="{ attrs }">
<v-btn <v-btn
text text
v-bind="attrs" v-bind="attrs"

View File

@@ -1,12 +1,11 @@
export default { export default {
name: 'Account', name: 'Admin',
data: () => ({ data: () => ({
currentUser: {}, currentUser: {},
changePasswordDialog: false, changePasswordDialog: false,
isPasswordValid: false, isPasswordValid: false,
password: '', password: '',
accentColor: {},
snackbar: false, snackbar: false,
snackbarText: '', snackbarText: '',
rules: { rules: {
@@ -22,7 +21,6 @@ export default {
.then(response => { .then(response => {
if (response.status === 200) { if (response.status === 200) {
this.currentUser = response.data.user this.currentUser = response.data.user
this.accentColor = this.currentUser.preferences.accentColor
} }
}) })
}, },
@@ -47,14 +45,6 @@ export default {
this.$http.patch('/users/dark-mode', { this.$http.patch('/users/dark-mode', {
darkMode: this.$vuetify.theme.dark darkMode: this.$vuetify.theme.dark
}) })
},
updateAccentColor () {
const preferences = { accentColor: this.accentColor }
this.$http.patch('/users/preferences', {
preferences: preferences
})
this.$vuetify.theme.themes.dark.primary = this.accentColor.hex
this.$vuetify.theme.themes.light.primary = this.accentColor.hex
} }
} }
} }

View File

@@ -6,9 +6,6 @@ export default {
isAdmin: false, isAdmin: false,
loggedIn: false loggedIn: false
}), }),
created () {
this.getUserPreferences()
},
methods: { methods: {
home () { home () {
this.$router.push('/') this.$router.push('/')
@@ -27,7 +24,7 @@ export default {
} }
}) })
}, },
checkForAdmin () { getCurrentUser () {
this.loggedIn = false this.loggedIn = false
this.drawyer = true this.drawyer = true
this.$http.get('/users/current') this.$http.get('/users/current')
@@ -35,21 +32,12 @@ export default {
if (response.status === 200) { if (response.status === 200) {
this.loggedIn = true this.loggedIn = true
this.isAdmin = response.data.user.isAdmin this.isAdmin = response.data.user.isAdmin
this.$vuetify.theme.dark = response.data.user.darkMode
} }
}) })
.catch(() => { .catch(() => {
this.isAdmin = false this.isAdmin = false
}) })
},
getUserPreferences () {
this.$http.get('/users/current')
.then(response => {
if (response.status === 200) {
const preferences = response.data.user.preferences
this.$vuetify.theme.themes.dark.primary = preferences.accentColor.hex
this.$vuetify.theme.themes.light.primary = preferences.accentColor.hex
}
})
} }
} }
} }

View File

@@ -1,6 +1,4 @@
export default { export default {
name: 'Login',
data: () => ({ data: () => ({
valid: false, valid: false,
username: '', username: '',

View File

@@ -64,18 +64,6 @@ export default {
previewSampleButtonText: 'Preview Sample', previewSampleButtonText: 'Preview Sample',
previewSampleLoading: true, previewSampleLoading: true,
previewSampleLength: 0, previewSampleLength: 0,
startRecordingDialog: false,
recordingDialog: false,
recordingTimeElapsed: 0,
recordedProfile: {},
recordingFileName: '',
isRecordingValid: false,
unsavedWork: false,
saveProfileText: 'Save Profile',
unwatch: null,
confirmSwitchProfileDialog: false,
activeProfile: {},
isSporadicValid: false,
errorSnackbar: false, errorSnackbar: false,
errorSnackbarText: '', errorSnackbarText: '',
rules: { rules: {
@@ -100,40 +88,6 @@ export default {
} }
}) })
return samples return samples
},
changeableSettings: function () {
const settings = [
this.isTimerEnabled,
this.hours,
this.minutes,
this.seconds,
this.volume,
this.noiseColor,
this.isFilterEnabled,
this.filterType,
this.filterCutoff,
this.isLFOFilterCutoffEnabled,
this.lfoFilterCutoffFrequency,
this.lfoFilterCutoffRange,
this.isTremoloEnabled,
this.tremoloDepth,
this.tremoloFrequency,
this.isTimerEnabled,
this.loadedSamples
]
this.loadedSamples.forEach(s => {
settings.push(s.volume)
settings.push(s.reverbEnabled)
settings.push(s.reverbPreDelay)
settings.push(s.reverbDecay)
settings.push(s.reverbWet)
settings.push(s.playbackMode)
settings.push(s.sporadicMin)
settings.push(s.sporadicMax)
})
return settings
} }
}, },
created () { created () {
@@ -144,7 +98,6 @@ export default {
this.players = new Tone.Players() this.players = new Tone.Players()
this.samplePreviewPlayer = new Tone.Player().toDestination() this.samplePreviewPlayer = new Tone.Player().toDestination()
this.samplePreviewPlayer.loop = true this.samplePreviewPlayer.loop = true
this.recorder = new Tone.Recorder()
this.populateProfileItems(0) this.populateProfileItems(0)
this.populatePreviewSampleItems() this.populatePreviewSampleItems()
@@ -155,13 +108,11 @@ export default {
this.stop() this.stop()
}, },
methods: { methods: {
async play () { play () {
if (!this.players.loaded) { if (!this.players.loaded) {
return return
} }
await Tone.start()
this.playDisabled = true this.playDisabled = true
Tone.Transport.cancel() Tone.Transport.cancel()
@@ -188,73 +139,35 @@ export default {
this.lfo.connect(this.filter.frequency).start() this.lfo.connect(this.filter.frequency).start()
} }
if (this.isTimerEnabled) {
this.duration = parseInt((this.hours * 3600)) + parseInt((this.minutes * 60)) + parseInt(this.seconds)
this.timeRemaining = this.duration
this.transportInterval = setInterval(() => this.stop(), this.duration * 1000 + 100)
this.timeRemainingInterval = setInterval(() => this.startTimer(), 1000)
Tone.Transport.loopEnd = this.duration
this.noise.sync().start(0).stop(this.duration)
} else {
this.noise.sync().start(0)
}
this.loadedSamples.forEach(s => { this.loadedSamples.forEach(s => {
this.players.player(s.id).loop = true this.players.player(s.id).loop = true
this.players.player(s.id).fadeIn = s.fadeIn this.players.player(s.id).fadeIn = s.fadeIn
if (s.loopPointsEnabled) { if (s.loopPointsEnabled) {
this.players.player(s.id).setLoopPoints(s.loopStart, s.loopEnd) this.players.player(s.id).setLoopPoints(s.loopStart, s.loopEnd)
} else {
this.players.player(s.id).setLoopPoints(0, this.players.player(s.id).buffer.duration)
} }
this.players.player(s.id).volume.value = s.volume this.players.player(s.id).volume.value = s.volume
this.players.player(s.id).disconnect()
if (s.reverbEnabled) {
const reverb = new Tone.Reverb(s.reverbDecay).toDestination()
reverb.set({ preDelay: s.reverbPreDelay, wet: s.reverbWet })
this.players.player(s.id).connect(reverb)
} else {
this.players.player(s.id).toDestination()
}
if (s.playbackMode === 'sporadic') {
this.players.player(s.id).loop = false
const maxInt = parseInt(s.sporadicMax, 10)
const minInt = parseInt(s.sporadicMin, 10)
if (minInt <= maxInt) {
const rand = Math.floor(Math.random() * (maxInt - minInt + 1) + minInt)
s.initialSporadicPlayInterval = setInterval(() => this.playSporadicSample(s.id), rand * 1000)
}
} else {
this.players.player(s.id).loop = true
if (this.isTimerEnabled) {
this.players.player(s.id).unsync().sync().start(0).stop(this.duration)
} else {
this.players.player(s.id).unsync().sync().start(0)
}
}
}) })
Tone.Transport.start('+0.1') if (this.isTimerEnabled) {
}, this.duration = parseInt((this.hours * 3600)) + parseInt((this.minutes * 60)) + parseInt(this.seconds)
playSporadicSample (id) { this.noise.sync().start(0).stop(this.duration)
const sample = this.loadedSamples.find(s => s.id === id) Tone.Transport.loopEnd = this.duration
this.timeRemaining = this.duration
this.transportInterval = setInterval(() => this.stop(), this.duration * 1000 + 100)
this.timeRemainingInterval = setInterval(() => this.startTimer(), 1000)
clearInterval(sample.initialSporadicPlayInterval) this.loadedSamples.forEach(s => {
clearInterval(sample.sporadicInterval) this.players.player(s.id).unsync().sync().start(0).stop(this.duration)
})
} else {
this.noise.sync().start(0)
this.players.player(id).unsync().sync().start() this.loadedSamples.forEach(s => {
this.players.player(s.id).unsync().sync().start(0)
})
}
const maxInt = parseInt(sample.sporadicMax, 10) Tone.Transport.start()
const minInt = parseInt(sample.sporadicMin, 10)
sample.playNextTime = Math.floor(Math.random() * (maxInt - minInt + 1) + minInt)
sample.sporadicInterval = setInterval(() => this.playSporadicSample(id), sample.playNextTime * 1000)
}, },
stop () { stop () {
clearInterval(this.transportInterval) clearInterval(this.transportInterval)
@@ -264,13 +177,6 @@ export default {
clearInterval(this.timeRemainingInterval) clearInterval(this.timeRemainingInterval)
this.timeRemaining = 0 this.timeRemaining = 0
this.duration = 0 this.duration = 0
this.loadedSamples.forEach(s => {
if (s.playbackMode === 'sporadic') {
clearInterval(s.initialSporadicPlayInterval)
clearInterval(s.sporadicInterval)
}
})
}, },
startTimer () { startTimer () {
this.timeRemaining -= 1 this.timeRemaining -= 1
@@ -343,8 +249,7 @@ export default {
this.selectedProfile = this.profileItems.find(p => p.id === profileId) this.selectedProfile = this.profileItems.find(p => p.id === profileId)
} }
this.exportedProfile = this.profileItems[0] this.exportedProfile = this.profileItems[0]
this.recordedProfile = this.profileItems[0] this.loadProfile()
this.loadProfile(true)
} }
} }
}) })
@@ -381,7 +286,6 @@ export default {
if (response.status === 200) { if (response.status === 200) {
this.profileDialog = false this.profileDialog = false
this.populateProfileItems(response.data.id) this.populateProfileItems(response.data.id)
this.unsavedWork = false
this.infoSnackbarText = 'Profile Saved' this.infoSnackbarText = 'Profile Saved'
this.infoSnackbar = true this.infoSnackbar = true
} }
@@ -410,7 +314,6 @@ export default {
samples: this.loadedSamples samples: this.loadedSamples
}).then(response => { }).then(response => {
if (response.status === 200) { if (response.status === 200) {
this.unsavedWork = false
this.infoSnackbarText = 'Profile Saved' this.infoSnackbarText = 'Profile Saved'
this.infoSnackbar = true this.infoSnackbar = true
} }
@@ -420,10 +323,7 @@ export default {
this.errorSnackbar = true this.errorSnackbar = true
}) })
}, },
loadProfile (checkForUnsavedWork) { loadProfile () {
if (checkForUnsavedWork && this.unsavedWork) {
this.confirmSwitchProfileDialog = true
} else {
this.$http.get('/profiles/'.concat(this.selectedProfile.id)) this.$http.get('/profiles/'.concat(this.selectedProfile.id))
.then(response => { .then(response => {
if (response.status === 200) { if (response.status === 200) {
@@ -445,23 +345,12 @@ export default {
this.tremoloDepth = profile.tremoloDepth this.tremoloDepth = profile.tremoloDepth
this.loadedSamples = profile.samples this.loadedSamples = profile.samples
this.activeProfile = profile
if (this.unwatch) {
this.unwatch()
}
this.unwatch = this.$watch('changeableSettings', function () {
this.unsavedWork = true
})
} }
}) })
.catch(() => { .catch(() => {
this.errorSnackbarText = 'Error Loading Profile' this.errorSnackbarText = 'Error Loading Profile'
this.errorSnackbar = true this.errorSnackbar = true
}) })
}
}, },
deleteProfile () { deleteProfile () {
this.$http.delete('/profiles/'.concat(this.selectedProfile.id)) this.$http.delete('/profiles/'.concat(this.selectedProfile.id))
@@ -525,8 +414,6 @@ export default {
this.checkedSamples.forEach(i => { this.checkedSamples.forEach(i => {
const load = this.allSamples.find(e => e.id === i) const load = this.allSamples.find(e => e.id === i)
load.volume = -10 load.volume = -10
load.sporadicMin = 30
load.sporadicMax = 300
this.loadedSamples.push(load) this.loadedSamples.push(load)
}) })
@@ -546,9 +433,6 @@ export default {
if (response.status === 200) { if (response.status === 200) {
this.canUpload = response.data.user.canUpload this.canUpload = response.data.user.canUpload
this.$vuetify.theme.dark = response.data.user.darkMode this.$vuetify.theme.dark = response.data.user.darkMode
const preferences = response.data.user.preferences
this.$vuetify.theme.themes.dark.primary = preferences.accentColor.hex
this.$vuetify.theme.themes.light.primary = preferences.accentColor.hex
} }
}) })
}, },
@@ -599,7 +483,7 @@ export default {
} }
}) })
.catch(() => { .catch(() => {
this.errorSnackbarText = 'Error Importing Profile' this.errorSnackbarText = 'Error Saving Profile'
this.errorSnackbar = true this.errorSnackbar = true
}) })
@@ -652,7 +536,7 @@ export default {
} }
}) })
.catch(() => { .catch(() => {
this.errorSnackbarText = 'Error Exporting Profile' this.errorSnackbarText = 'Error Loading Profile'
this.errorSnackbar = true this.errorSnackbar = true
}) })
@@ -709,13 +593,6 @@ export default {
} }
}) })
}, },
updatePreviewSampleLoopPoints () {
if (this.previewSampleLoopPointsEnabled) {
this.samplePreviewPlayer.setLoopPoints(this.previewSampleLoopStart, this.previewSampleLoopEnd)
} else {
this.samplePreviewPlayer.setLoopPoints(0, this.samplePreviewPlayer.buffer.duration)
}
},
previewSample () { previewSample () {
if (this.previewSamplePlaying) { if (this.previewSamplePlaying) {
this.previewSamplePlaying = false this.previewSamplePlaying = false
@@ -736,16 +613,7 @@ export default {
}).then(response => { }).then(response => {
if (response.status === 200) { if (response.status === 200) {
this.getSamples() this.getSamples()
this.loadProfile()
// Update sample if it's already loaded in current profile
const sample = this.loadedSamples.find(s => s.id === this.selectedPreviewSample.id)
if (sample) {
sample.fadeIn = this.previewSampleFadeIn
sample.loopPointsEnabled = this.previewSampleLoopPointsEnabled
sample.loopStart = this.previewSampleLoopStart
sample.loopEnd = this.previewSampleLoopEnd
}
this.closeEditSampleForm() this.closeEditSampleForm()
this.infoSnackbarText = 'Sample Saved' this.infoSnackbarText = 'Sample Saved'
this.infoSnackbar = true this.infoSnackbar = true
@@ -763,161 +631,6 @@ export default {
if (this.previewSampleLoopStart >= 0 && this.previewSampleLoopEnd <= this.previewSampleLength) { if (this.previewSampleLoopStart >= 0 && this.previewSampleLoopEnd <= this.previewSampleLength) {
this.samplePreviewPlayer.setLoopPoints(this.previewSampleLoopStart, this.previewSampleLoopEnd) this.samplePreviewPlayer.setLoopPoints(this.previewSampleLoopStart, this.previewSampleLoopEnd)
} }
},
openStartRecordingDialog () {
this.startRecordingDialog = true
this.profileMoreDialog = false
},
startRecording () {
// Save current profile before recording
this.updateProfile()
this.$http.get('/profiles/'.concat(this.recordedProfile.id))
.then(async response => {
if (response.status === 200) {
const profile = response.data.profile
this.isTimerEnabled = profile.isTimerEnabled
this.duration = profile.duration
this.volume = profile.volume
this.noiseColor = profile.noiseColor
this.isFilterEnabled = profile.isFilterEnabled
this.filterType = profile.filterType
this.filterCutoff = profile.filterCutoff
this.isLFOFilterCutoffEnabled = profile.isLFOFilterCutoffEnabled
this.lfoFilterCutoffFrequency = profile.lfoFilterCutoffFrequency
this.lfoFilterCutoffRange[0] = profile.lfoFilterCutoffLow
this.lfoFilterCutoffRange[1] = profile.lfoFilterCutoffHigh
this.isTremoloEnabled = profile.isTremoloEnabled
this.tremoloFrequency = profile.tremoloFrequency
this.tremoloDepth = profile.tremoloDepth
this.loadedSamples = profile.samples
this.startRecordingDialog = false
this.recordingDialog = true
this.recordingTimeElapsed = 0
await this.recorder.start()
this.recordingInterval = setInterval(() => this.recordingTimeElapsed++, 1000)
this.playProfileForRecording()
}
})
.catch(() => {
this.errorSnackbarText = 'Error Recording Profile'
this.errorSnackbar = true
})
},
playProfileForRecording () {
this.playDisabled = true
Tone.Transport.cancel()
if (!this.isFilterEnabled && !this.isTremoloEnabled) {
this.noise = new Tone.Noise({ volume: this.volume, type: this.noiseColor }).connect(this.recorder).toDestination()
} else if (!this.isFilterEnabled && this.isTremoloEnabled) {
this.tremolo = new Tone.Tremolo({ frequency: this.tremoloFrequency, depth: this.tremoloDepth }).connect(this.recorder).toDestination().start()
this.noise = new Tone.Noise({ volume: this.volume, type: this.noiseColor }).connect(this.tremolo)
} else if (this.isFilterEnabled && !this.isTremoloEnabled) {
this.filter = new Tone.Filter(this.filterCutoff, this.filterType).connect(this.recorder).toDestination()
this.noise = new Tone.Noise({ volume: this.volume, type: this.noiseColor }).connect(this.filter)
} else if (this.isFilterEnabled && this.isTremoloEnabled) {
this.tremolo = new Tone.Tremolo({ frequency: this.tremoloFrequency, depth: this.tremoloDepth }).connect(this.recorder).toDestination().start()
this.filter = new Tone.Filter(this.filterCutoff, this.filterType).connect(this.tremolo)
this.noise = new Tone.Noise({ volume: this.volume, type: this.noiseColor }).connect(this.filter)
} else {
this.tremolo = new Tone.Tremolo({ frequency: this.tremoloFrequency, depth: this.tremoloDepth }).connect(this.recorder).toDestination().start()
this.filter = new Tone.Filter(this.filterCutoff, this.filterType).connect(this.tremolo)
this.noise = new Tone.Noise({ volume: this.volume, type: this.noiseColor }).connect(this.filter)
}
if (this.isLFOFilterCutoffEnabled) {
this.lfo = new Tone.LFO({ frequency: this.lfoFilterCutoffFrequency, min: this.lfoFilterCutoffRange[0], max: this.lfoFilterCutoffRange[1] })
this.lfo.connect(this.filter.frequency).start()
}
this.loadedSamples.forEach(s => {
this.players.player(s.id).loop = true
this.players.player(s.id).fadeIn = s.fadeIn
if (s.loopPointsEnabled) {
this.players.player(s.id).setLoopPoints(s.loopStart, s.loopEnd)
} else {
this.players.player(s.id).setLoopPoints(0, this.players.player(s.id).buffer.duration)
}
this.players.player(s.id).volume.value = s.volume
this.players.player(s.id).disconnect()
if (s.reverbEnabled) {
const reverb = new Tone.Reverb(s.reverbDecay).connect(this.recorder).toDestination()
reverb.set({ preDelay: s.reverbPreDelay, wet: s.reverbWet })
this.players.player(s.id).connect(reverb)
} else {
this.players.player(s.id).connect(this.recorder).toDestination()
}
})
this.noise.sync().start(0)
this.loadedSamples.forEach(s => {
if (s.playbackMode === 'sporadic') {
this.players.player(s.id).loop = false
const maxInt = parseInt(s.sporadicMax, 10)
const minInt = parseInt(s.sporadicMin, 10)
const rand = Math.floor(Math.random() * (maxInt - minInt + 1) + minInt)
s.initialSporadicPlayInterval = setInterval(() => this.playSporadicSample(s.id), rand * 1000)
} else {
this.players.player(s.id).loop = true
this.players.player(s.id).unsync().sync().start(0)
}
})
Tone.Transport.start('+0.1')
},
async stopRecording () {
const recording = await this.recorder.stop()
// Set active profile back to the selected one
this.loadProfile(false)
const url = URL.createObjectURL(recording)
const anchor = document.createElement('a')
anchor.download = this.recordingFileName + '.webm'
anchor.href = url
anchor.click()
clearInterval(this.recordingInterval)
this.loadedSamples.forEach(s => {
if (s.playbackMode === 'sporadic') {
clearInterval(s.initialSporadicPlayInterval)
clearInterval(s.sporadicInterval)
}
})
this.recordingDialog = false
this.stop()
},
async cancelRecording () {
await this.recorder.stop()
// Set active profile back to the selected one
this.loadProfile(false)
clearInterval(this.recordingInterval)
this.recordingDialog = false
this.stop()
},
discardChanges () {
this.unsavedWork = false
this.loadProfile(true)
this.confirmSwitchProfileDialog = false
},
saveChanges () {
// Set active profile back to previously selected one before saving
this.selectedProfile = this.profileItems.find(p => p.text === this.activeProfile.name)
this.updateProfile()
this.confirmSwitchProfileDialog = false
} }
} }
} }

View File

@@ -1,6 +1,4 @@
export default { export default {
name: 'Register',
data: () => ({ data: () => ({
valid: false, valid: false,
name: '', name: '',

View File

@@ -1,5 +1,5 @@
import Vue from 'vue' import Vue from 'vue'
import Vuetify from 'vuetify/lib' import Vuetify from 'vuetify/lib/framework'
import colors from 'vuetify/lib/util/colors' import colors from 'vuetify/lib/util/colors'

View File

@@ -1,15 +1,15 @@
<template> <template>
<AccountPage /> <Account />
</template> </template>
<script> <script>
import AccountPage from '../components/AccountPage' import Account from '../components/Account'
export default { export default {
name: 'AccountView', name: 'AccountView',
components: { components: {
AccountPage Account
} }
} }
</script> </script>

View File

@@ -1,15 +1,15 @@
<template> <template>
<AdminPage /> <Admin />
</template> </template>
<script> <script>
import AdminPage from '../components/AdminPage' import Admin from '../components/Admin'
export default { export default {
name: 'AdminView', name: 'AdminView',
components: { components: {
AdminPage Admin
} }
} }
</script> </script>

View File

@@ -1,15 +1,15 @@
<template> <template>
<NoisePage /> <Noise />
</template> </template>
<script> <script>
import NoisePage from '../components/NoisePage' import Noise from '../components/Noise'
export default { export default {
name: 'HomeView', name: 'HomeView',
components: { components: {
NoisePage Noise
} }
} }
</script> </script>

View File

@@ -1,15 +1,15 @@
<template> <template>
<LoginPage /> <Login />
</template> </template>
<script> <script>
import LoginPage from '../components/LoginPage' import Login from '../components/Login'
export default { export default {
name: 'LoginView', name: 'LoginView',
components: { components: {
LoginPage Login
} }
} }
</script> </script>

View File

@@ -1,15 +1,15 @@
<template> <template>
<RegisterPage /> <Register />
</template> </template>
<script> <script>
import RegisterPage from '../components/RegisterPage' import Register from '../components/Register'
export default { export default {
name: 'RegisterView', name: 'RegisterView',
components: { components: {
RegisterPage Register
} }
} }
</script> </script>