Add sample support, re-organize repo

This commit is contained in:
Kevin Thomas
2021-09-24 00:35:18 -07:00
parent b9e340c26a
commit f2d1b80673
23 changed files with 703 additions and 524 deletions

9
package-lock.json generated
View File

@@ -5403,6 +5403,15 @@
}
}
},
"eslint-plugin-html": {
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-html/-/eslint-plugin-html-6.1.2.tgz",
"integrity": "sha512-bhBIRyZFqI4EoF12lGDHAmgfff8eLXx6R52/K3ESQhsxzCzIE6hdebS7Py651f7U3RBotqroUnC3L29bR7qJWQ==",
"dev": true,
"requires": {
"htmlparser2": "^6.0.1"
}
},
"eslint-plugin-import": {
"version": "2.23.4",
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.23.4.tgz",

View File

@@ -35,6 +35,7 @@
"@vue/eslint-config-standard": "^5.1.2",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-html": "^6.1.2",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",

View File

@@ -8,7 +8,9 @@ module.exports = function () {
hashed_password BLOB,
salt BLOB,
name TEXT,
is_admin INTEGER)`
is_admin INTEGER,
dark_mode INTEGER,
can_upload INTEGER)`
)
db.run(`CREATE TABLE IF NOT EXISTS profiles (
@@ -35,7 +37,6 @@ module.exports = function () {
db.run(`CREATE TABLE IF NOT EXISTS samples (
id INTEGER PRIMARY KEY,
name TEXT UNIQUE,
volume INTEGER,
user INTEGER,
FOREIGN KEY(user) REFERENCES users(id))`
)
@@ -44,6 +45,7 @@ module.exports = function () {
id INTEGER PRIMARY KEY,
profile INTEGER,
sample INTEGER,
volume INTEGER,
FOREIGN KEY(profile) REFERENCES profiles(id),
FOREIGN KEY(sample) REFERENCES samples(id))`
)

View File

@@ -47,27 +47,26 @@ router.post('/profiles', (req, res) => {
],
function (err) {
if (err) {
return res.sendStatus(500)
if (err.code === 'SQLITE_CONSTRAINT') {
return res.sendStatus(409)
} else {
return res.sendStatus(500)
}
}
profileID = this.lastID
req.body.samples.forEach(s => {
db.run('INSERT INTO profiles_samples (profile, sample) VALUES (?, ?)', [
db.run('INSERT INTO profiles_samples (profile, sample, volume) VALUES (?, ?, ?)', [
profileID,
s.id
s.id,
s.volume
],
(err) => {
if (err) {
return res.sendStatus(500)
}
})
db.run('UPDATE samples SET volume = ? WHERE id = ?', [s.volume, s.id], (err) => {
if (err) {
return res.sendStatus(500)
}
})
})
return res.json({ id: profileID })
@@ -75,6 +74,88 @@ router.post('/profiles', (req, res) => {
})
})
router.put('/profiles/:profileId', (req, res) => {
if (!req.user) {
return res.sendStatus(401)
}
db.serialize(() => {
db.get('SELECT user FROM profiles WHERE id = ?', [req.params.profileId], (err, row) => {
if (err) {
return res.sendStatus(500)
}
if (row.user.toString() !== req.user.id) {
return res.sendStatus(401)
}
})
db.run(`UPDATE profiles SET
timer_enabled = ?,
duration = ?,
volume = ?,
noise_color = ?,
filter_enabled = ?,
filter_type = ?,
filter_cutoff = ?,
lfo_filter_cutoff_enabled = ?,
lfo_filter_cutoff_frequency = ?,
lfo_filter_cutoff_low = ?,
lfo_filter_cutoff_high = ?,
tremolo_enabled = ?,
tremolo_frequency = ?,
tremolo_depth = ?
WHERE id = ?`, [
req.body.isTimerEnabled ? 1 : 0,
req.body.duration,
req.body.volume,
req.body.noiseColor,
req.body.isFilterEnabled ? 1 : 0,
req.body.filterType,
req.body.filterCutoff,
req.body.isLFOFilterCutoffEnabled ? 1 : 0,
req.body.lfoFilterCutoffFrequency,
req.body.lfoFilterCutoffLow,
req.body.lfoFilterCutoffHigh,
req.body.isTremoloEnabled ? 1 : 0,
req.body.tremoloFrequency,
req.body.tremoloDepth,
req.params.profileId
],
(err) => {
if (err) {
console.log(err)
return res.sendStatus(500)
}
db.serialize(() => {
db.run('DELETE FROM profiles_samples WHERE profile = ?', [
req.params.profileId
],
(err) => {
if (err) {
return res.sendStatus(500)
}
})
req.body.samples.forEach(s => {
db.run('INSERT INTO profiles_samples (profile, sample, volume) VALUES (?, ?, ?)', [
req.params.profileId,
s.id,
s.volume
],
(err) => {
if (err) {
return res.sendStatus(500)
}
})
})
})
return res.sendStatus(200)
})
})
})
router.post('/profiles/default', (req, res) => {
if (!req.user) {
return res.sendStatus(401)
@@ -117,13 +198,13 @@ router.get('/profiles', (req, res) => {
return res.sendStatus(401)
}
const profiles = []
db.all('SELECT id, name FROM profiles WHERE user = ?', [req.user.id], (err, rows) => {
if (err) {
return res.sendStatus(500)
}
const profiles = []
rows.forEach(row => {
const profile = {}
@@ -142,8 +223,6 @@ router.get('/profiles/:profileId', (req, res) => {
return res.sendStatus(401)
}
const profile = {}
db.serialize(() => {
db.get(`SELECT
name,
@@ -164,6 +243,7 @@ router.get('/profiles/:profileId', (req, res) => {
tremolo_depth as tremoloDepth
FROM profiles WHERE id = ?`, [req.params.profileId], (err, row) => {
if (err) {
console.log(err)
return res.sendStatus(500)
}
@@ -171,6 +251,8 @@ router.get('/profiles/:profileId', (req, res) => {
return res.sendStatus(401)
}
const profile = {}
profile.name = row.name
profile.isTimerEnabled = row.isTimerEnabled === 1
profile.duration = row.duration
@@ -187,24 +269,34 @@ router.get('/profiles/:profileId', (req, res) => {
profile.tremoloFrequency = row.tremoloFrequency
profile.tremoloDepth = row.tremoloDepth
const sampleIds = []
db.all('SELECT sample FROM profiles_samples WHERE profile = ?', [req.params.profileId], (err, rows) => {
if (err) {
console.log(err)
return res.sendStatus(500)
}
const samples = []
const sampleQueryArgs = []
sampleQueryArgs.push(req.params.profileId)
rows.forEach(row => {
sampleIds.push(row.sample)
sampleQueryArgs.push(row.sample)
})
db.all('SELECT id, name, volume FROM samples WHERE id IN ( ' + sampleIds.map(() => { return '?' }).join(',') + ' )', sampleIds, (err, rows) => {
db.all(`SELECT samples.id, name, profiles_samples.volume
FROM samples
INNER JOIN profiles_samples
ON profiles_samples.sample = samples.id
AND profiles_samples.profile = ?
WHERE samples.id IN ( ` +
sampleQueryArgs.map(() => { return '?' }).join(',') + ' )', sampleQueryArgs, (err, rows) => {
if (err) {
console.log(err)
return res.sendStatus(500)
}
const samples = []
rows.forEach(row => {
const sample = {}

View File

@@ -3,8 +3,8 @@ const config = require('config')
const multer = require('multer')
const storage = multer.diskStorage({
destination: config.get('Server.sampleUploadPath'),
filename: function (req, file, cb) {
cb(null, req.body.name)
filename: (req, file, cb) => {
cb(null, req.user.id + '_' + req.body.name)
}
})
const upload = multer({ storage: storage })
@@ -16,17 +16,33 @@ router.post('/samples', upload.single('sample'), (req, res, next) => {
return res.sendStatus(401)
}
db.run('INSERT INTO samples (name, volume, user) VALUES (?, ?, ?)', [
req.body.name,
0,
req.user.id
],
(err) => {
if (err) {
return res.sendStatus(500)
} else {
return res.sendStatus(200)
}
db.serialize(() => {
db.get('SELECT can_upload FROM users WHERE id = ?', [req.user.id], (err, row) => {
if (err) {
return res.sendStatus(500)
}
if (row.can_upload === 0) {
return res.sendStatus(401)
}
})
db.run('INSERT INTO samples (name, user) VALUES (?, ?)', [
req.body.name,
req.user.id
],
(err) => {
if (err) {
console.log(err)
if (err.code === 'SQLITE_CONSTRAINT') {
return res.sendStatus(409)
} else {
return res.sendStatus(500)
}
} else {
return res.sendStatus(200)
}
})
})
})
@@ -37,8 +53,9 @@ router.get('/samples', (req, res) => {
const samples = []
db.all('SELECT id, name, volume FROM samples WHERE user = ?', [req.user.id], (err, rows) => {
db.all('SELECT id, name FROM samples WHERE user = ?', [req.user.id], (err, rows) => {
if (err) {
console.log(err)
return res.sendStatus(500)
}
@@ -47,7 +64,7 @@ router.get('/samples', (req, res) => {
sample.id = row.id
sample.name = row.name
sample.volume = row.volume
sample.user = req.user.id
samples.push(sample)
})

View File

@@ -8,17 +8,21 @@ router.get('/users/current', (req, res) => {
return res.sendStatus(401)
}
db.get('SELECT is_admin as isAdmin, * FROM users WHERE id = ?', [req.user.id], (err, row) => {
db.get('SELECT is_admin as isAdmin, dark_mode as darkMode, can_upload as canUpload, * FROM users WHERE id = ?', [req.user.id], (err, row) => {
if (err) {
return res.sendStatus(500)
}
const user = {}
user.id = row.id
user.username = row.username
user.name = row.name
user.isAdmin = row.isAdmin === 1
if (row) {
user.id = row.id
user.username = row.username
user.name = row.name
user.isAdmin = row.isAdmin === 1
user.darkMode = row.darkMode === 1
user.canUpload = row.canUpload === 1
}
res.json({ user: user })
})
@@ -31,7 +35,7 @@ router.get('/users', (req, res) => {
const users = []
db.all('SELECT id, username, name, is_admin as isAdmin FROM users', (err, rows) => {
db.all('SELECT id, username, name, is_admin as isAdmin, can_upload as canUpload FROM users', (err, rows) => {
if (err) {
return res.sendStatus(500)
}
@@ -43,6 +47,7 @@ router.get('/users', (req, res) => {
user.username = row.username
user.name = row.name
user.isAdmin = row.isAdmin === 1
user.canUpload = row.canUpload === 1
users.push(user)
})
@@ -58,12 +63,15 @@ router.post('/users', (req, res) => {
return res.sendStatus(500)
}
db.run('INSERT INTO users (username, hashed_password, salt, name, is_admin) VALUES (?, ?, ?, ?, ?)', [
db.run(`INSERT INTO users (username, hashed_password, salt, name, is_admin, dark_mode, can_upload)
VALUES (?, ?, ?, ?, ?, ?, ?)`, [
req.body.username,
hashedPassword,
salt,
req.body.name,
req.body.isAdmin
req.body.isAdmin,
req.body.darkMode,
req.body.canUpload
], function (err) {
if (err) {
if (err.code === 'SQLITE_CONSTRAINT') {
@@ -89,7 +97,7 @@ router.post('/users', (req, res) => {
})
})
router.patch('/users/:userId', (req, res) => {
router.patch('/users/admin/:userId', (req, res) => {
if (!req.user) {
return res.sendStatus(401)
}
@@ -115,6 +123,48 @@ router.patch('/users/:userId', (req, res) => {
})
})
router.patch('/users/upload/:userId', (req, res) => {
if (!req.user) {
return res.sendStatus(401)
}
db.serialize(() => {
db.get('SELECT is_admin FROM users WHERE id = ?', [req.user.id], (err, row) => {
if (err) {
return res.sendStatus(500)
}
if (row.is_admin === 0) {
return res.sendStatus(401)
}
})
db.run('UPDATE users SET can_upload = ? WHERE id = ?', [req.body.canUpload ? 1 : 0, req.params.userId], (err) => {
if (err) {
return res.sendStatus(500)
} else {
return res.sendStatus(200)
}
})
})
})
router.patch('/users/dark-mode', (req, res) => {
if (!req.user) {
return res.sendStatus(401)
}
db.serialize(() => {
db.run('UPDATE users SET dark_mode = ? WHERE id = ?', [req.body.darkMode ? 1 : 0, req.user.id], (err) => {
if (err) {
return res.sendStatus(500)
} else {
return res.sendStatus(200)
}
})
})
})
router.delete('/users/:userId', (req, res) => {
if (!req.user) {
return res.sendStatus(401)

View File

@@ -1,124 +1,96 @@
<template>
<v-container>
<v-row class="text-left">
<v-row class="text-center">
<v-col class="mb-5">
<h1 class="display-2 font-weight-bold mb-3">
Admin Dashboard
</h1>
</v-col>
</v-row>
<v-col cols="12">
<v-simple-table>
<thead>
<tr>
<th class="text-left">
ID
</th>
<th class="text-left">
Username
</th>
<th class="text-left">
Is Admin
</th>
<th class="text-left">
Delete User
</th>
</tr>
</thead>
<tbody>
<tr
v-for="user in users"
:key="user.username"
>
<td>{{ user.id }}</td>
<td>{{ user.username }}</td>
<td>
<v-switch
v-model="user.isAdmin"
:label="`${user.isAdmin ? 'True' : 'False'}`"
@change="updateUser(user.id, user.isAdmin); snackbar = true"
/>
</td>
<td>
<v-btn
@click="deleteUser(user.id)"
>
Delete
</v-btn>
</td>
</tr>
</tbody>
</v-simple-table>
<v-snackbar
v-model="snackbar"
timeout="3000"
>
{{ updateText }}
<template v-slot:action="{ attrs }">
<v-btn
text
v-bind="attrs"
@click="snackbar = false"
>
Close
</v-btn>
</template>
</v-snackbar>
<v-row class="text-center">
<v-col class="mb-5">
<h2 class="headline font-weight-bold">
Users
</h2>
</v-col>
</v-row>
<v-col cols="12">
<v-row
v-for="user in users"
:key="user.username"
>
<v-container>
<v-row
justify="center"
>
ID: {{ user.id }}
</v-row>
<v-row
justify="center"
>
Username: {{ user.username }}
</v-row>
<v-row
justify="center"
>
Name: {{ user.name }}
</v-row>
<v-row
justify="center"
>
<v-switch
v-model="user.isAdmin"
:label="`${user.isAdmin ? 'Admin' : 'Not Admin'}`"
:disabled="user.id === currentUser.id"
@change="updateUserAdmin(user.id, user.isAdmin); snackbar = true"
/>
</v-row>
<v-row
justify="center"
>
<v-switch
v-model="user.canUpload"
:label="`${user.canUpload ? 'Can Upload Samples' : 'Cannot Upload Samples'}`"
:disabled="user.id === currentUser.id"
@change="updateUserUpload(user.id, user.canUpload); snackbar = true"
/>
</v-row>
<v-row
justify="center"
>
<v-btn
:disabled="user.id === currentUser.id"
@click="deleteUser(user.id)"
>
Delete
</v-btn>
</v-row>
<v-divider
class="mt-7"
/>
</v-container>
</v-row>
<v-snackbar
v-model="snackbar"
timeout="3000"
>
{{ updateText }}
<template v-slot:action="{ attrs }">
<v-btn
text
v-bind="attrs"
@click="snackbar = false"
>
Close
</v-btn>
</template>
</v-snackbar>
</v-col>
</v-container>
</template>
<script>
export default {
name: 'Admin',
data: () => ({
users: [],
snackbar: false,
updateText: ''
}),
created () {
this.getUsers()
},
methods: {
getUsers () {
this.$http.get('/users')
.then(response => {
if (response.status === 200) {
this.users = response.data.users
}
})
.catch((error) => {
console.error(error.response)
})
},
updateUser (id, isAdmin) {
this.$http.patch('/users/'.concat(id), {
isAdmin: isAdmin
})
.then(response => {
if (response.status === 200) {
this.updateText = 'User updated'
}
})
.catch(function (error) {
console.error(error.response)
this.updateText = 'Error updating user'
})
},
deleteUser (id) {
this.$http.delete('/users/'.concat(id))
.then(response => {
if (response.status === 200) {
this.getUsers()
}
})
.catch((error) => {
console.error(error.response)
})
}
}
}
</script>
<script src="./admin.js"></script>

View File

@@ -7,7 +7,7 @@
dense
>
<v-app-bar-nav-icon
@click="openDrawyer"
@click="drawyer = true"
/>
</v-app-bar>
<v-navigation-drawer
@@ -54,6 +54,7 @@
<v-switch
v-model="$vuetify.theme.dark"
label="Dark Mode"
@change="toggleDarkMode"
/>
</v-list-item>
</v-list-item-group>
@@ -62,47 +63,4 @@
</v-container>
</template>
<script>
export default {
name: 'AppBar',
data: () => ({
drawyer: false,
isAdmin: false
}),
methods: {
home () {
this.$router.push('/')
},
admin () {
this.$router.push('/admin')
},
logout () {
this.$http.get('/logout')
.then(response => {
if (response.status === 200) {
this.$router.push('/login')
}
})
.catch((error) => {
console.error(error.response)
})
},
openDrawyer () {
this.$http.get('/users/current')
.then(response => {
if (response.data.user.isAdmin) {
this.isAdmin = true
} else {
this.isAdmin = false
}
})
.catch(function (error) {
console.error(error.response)
this.isAdmin = false
})
this.drawyer = true
}
}
}
</script>
<script src="./appbar.js"></script>

View File

@@ -1,151 +0,0 @@
<template>
<v-container>
<v-row class="text-center">
<v-col cols="12">
<v-img
:src="require('../assets/logo.svg')"
class="my-3"
contain
height="200"
/>
</v-col>
<v-col class="mb-4">
<h1 class="display-2 font-weight-bold mb-3">
Welcome to Vuetify
</h1>
<p class="subheading font-weight-regular">
For help and collaboration with other Vuetify developers,
<br>please join our online
<a
href="https://community.vuetifyjs.com"
target="_blank"
>Discord Community</a>
</p>
</v-col>
<v-col
class="mb-5"
cols="12"
>
<h2 class="headline font-weight-bold mb-3">
What's next?
</h2>
<v-row justify="center">
<a
v-for="(next, i) in whatsNext"
:key="i"
:href="next.href"
class="subheading mx-3"
target="_blank"
>
{{ next.text }}
</a>
</v-row>
</v-col>
<v-col
class="mb-5"
cols="12"
>
<h2 class="headline font-weight-bold mb-3">
Important Links
</h2>
<v-row justify="center">
<a
v-for="(link, i) in importantLinks"
:key="i"
:href="link.href"
class="subheading mx-3"
target="_blank"
>
{{ link.text }}
</a>
</v-row>
</v-col>
<v-col
class="mb-5"
cols="12"
>
<h2 class="headline font-weight-bold mb-3">
Ecosystem
</h2>
<v-row justify="center">
<a
v-for="(eco, i) in ecosystem"
:key="i"
:href="eco.href"
class="subheading mx-3"
target="_blank"
>
{{ eco.text }}
</a>
</v-row>
</v-col>
</v-row>
</v-container>
</template>
<script>
export default {
name: 'HelloWorld',
data: () => ({
ecosystem: [
{
text: 'vuetify-loader',
href: 'https://github.com/vuetifyjs/vuetify-loader'
},
{
text: 'github',
href: 'https://github.com/vuetifyjs/vuetify'
},
{
text: 'awesome-vuetify',
href: 'https://github.com/vuetifyjs/awesome-vuetify'
}
],
importantLinks: [
{
text: 'Documentation',
href: 'https://vuetifyjs.com'
},
{
text: 'Chat',
href: 'https://community.vuetifyjs.com'
},
{
text: 'Made with Vuetify',
href: 'https://madewithvuejs.com/vuetify'
},
{
text: 'Twitter',
href: 'https://twitter.com/vuetifyjs'
},
{
text: 'Articles',
href: 'https://medium.com/vuetify'
}
],
whatsNext: [
{
text: 'Explore components',
href: 'https://vuetifyjs.com/components/api-explorer'
},
{
text: 'Select a layout',
href: 'https://vuetifyjs.com/getting-started/pre-made-layouts'
},
{
text: 'Frequently Asked Questions',
href: 'https://vuetifyjs.com/getting-started/frequently-asked-questions'
}
]
})
}
</script>

View File

@@ -46,34 +46,4 @@
</v-form>
</template>
<script>
export default {
data: () => ({
valid: false,
username: '',
password: '',
usernameRules: [
v => !!v || 'Username is required'
],
passwordRules: [
v => !!v || 'Password is required'
]
}),
methods: {
login () {
this.$http.post('/login/password', {
username: this.username,
password: this.password
})
.then(response => {
if (response.status === 200) {
this.$router.push('/')
}
})
.catch((error) => {
console.error(error.response)
})
}
}
}
</script>
<script src="./login.js"></script>

View File

@@ -8,6 +8,10 @@
</v-col>
<v-col cols="12">
<h2 class="headline font-weight-bold mb-10">
Playback
</h2>
<v-row justify="center">
<v-btn
:disabled="playDisabled || !isTimerValid"
@@ -32,6 +36,109 @@
</v-row>
</v-col>
<v-col cols="12">
<h2 class="headline font-weight-bold mb-5">
Profiles
</h2>
<v-row justify="center">
<v-select
v-model="selectedProfile"
:items="profileItems"
return-object
label="Profiles"
class="mx-3 mb-5"
@change="loadProfile"
/>
</v-row>
<v-btn
class="mx-3"
:disabled="profileItems.length < 2"
@click="deleteProfile"
>
Delete Profile
</v-btn>
<v-btn
class="mx-3"
@click="updateProfile"
>
Save Profile
</v-btn>
<v-snackbar
v-model="updateProfileSnackbar"
timeout="3000"
>
{{ updateProfileText }}
<template v-slot:action="{ attrs }">
<v-btn
text
v-bind="attrs"
@click="updateProfileSnackbar = false"
>
Close
</v-btn>
</template>
</v-snackbar>
<v-dialog
v-model="profileDialog"
max-width="600px"
>
<template v-slot:activator="{ on, attrs }">
<v-btn
v-bind="attrs"
class="mx-3"
v-on="on"
@click="profileName = ''"
>
Save Profile As...
</v-btn>
</template>
<v-form
v-model="isProfileValid"
>
<v-card>
<v-card-title>
<span class="text-h5">Profile Name</span>
</v-card-title>
<v-card-text>
<v-container>
<v-row>
<v-col cols="12">
<v-text-field
v-model="profileName"
label="Profile Name"
:rules="[rules.required()]"
/>
</v-col>
</v-row>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
text
@click="profileDialog = false"
>
Close
</v-btn>
<v-btn
text
:disabled="!isProfileValid"
@click="saveProfile"
>
Save
</v-btn>
</v-card-actions>
</v-card>
</v-form>
</v-dialog>
</v-col>
<v-col cols="12">
<h2 class="headline font-weight-bold mb-5">
Timer
@@ -273,7 +380,10 @@
</v-row>
</v-col>
<v-col cols="12">
<v-col
v-if="canUpload"
cols="12"
>
<h2 class="headline font-weight-bold mb-5">
Samples
</h2>
@@ -290,6 +400,14 @@
</v-row>
<v-row>
<v-btn
icon
:disabled="playDisabled"
@click="removeSample(index)"
>
<v-icon>mdi-delete</v-icon>
</v-btn>
<v-slider
v-model="sample.volume"
label="Volume"
@@ -434,83 +552,23 @@
</v-dialog>
</v-col>
<v-col cols="12">
<h2 class="headline font-weight-bold mb-5">
Profiles
</h2>
<v-snackbar
v-model="errorSnackbar"
color="error"
timeout="3000"
>
{{ errorSnackbarText }}
<v-row justify="center">
<v-select
v-model="selectedProfile"
:items="profileItems"
return-object
label="Profiles"
class="mx-3 mb-5"
@change="loadProfile"
/>
</v-row>
<v-btn
class="mx-3"
:disabled="profileItems.length < 2"
@click="deleteProfile"
>
Delete Profile
</v-btn>
<v-dialog
v-model="profileDialog"
max-width="600px"
>
<template v-slot:activator="{ on, attrs }">
<v-btn
v-bind="attrs"
class="mx-3"
v-on="on"
>
Save Profile
</v-btn>
</template>
<v-form
v-model="isProfileValid"
<template v-slot:action="{ attrs }">
<v-btn
text
v-bind="attrs"
@click="errorSnackbar = false"
>
<v-card>
<v-card-title>
<span class="text-h5">Profile Name</span>
</v-card-title>
<v-card-text>
<v-container>
<v-row>
<v-col cols="12">
<v-text-field
v-model="profileName"
label="Profile Name"
:rules="[rules.required()]"
/>
</v-col>
</v-row>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
text
@click="profileDialog = false"
>
Close
</v-btn>
<v-btn
text
:disabled="!isProfileValid"
@click="saveProfile"
>
Save
</v-btn>
</v-card-actions>
</v-card>
</v-form>
</v-dialog>
</v-col>
Close
</v-btn>
</template>
</v-snackbar>
</v-row>
</v-container>
</template>

View File

@@ -57,34 +57,4 @@
</v-form>
</template>
<script>
export default {
data: () => ({
valid: false,
name: '',
username: '',
password: '',
rules: {
required: v => !!v || 'Required'
}
}),
methods: {
register () {
this.$http.post('/users', {
name: this.name,
username: this.username,
password: this.password,
isAdmin: 1
})
.then(response => {
if (response.status === 200) {
this.$router.push('/login')
}
})
.catch((error) => {
console.error(error.response)
})
}
}
}
</script>
<script src="./register.js"></script>

77
src/components/admin.js Normal file
View File

@@ -0,0 +1,77 @@
export default {
name: 'Admin',
data: () => ({
currentUser: {},
users: [],
snackbar: false,
updateText: ''
}),
created () {
this.getCurrentUser()
this.getUsers()
},
methods: {
getUsers () {
this.$http.get('/users')
.then(response => {
if (response.status === 200) {
this.users = response.data.users
}
})
.catch((error) => {
console.error(error.response)
})
},
getCurrentUser () {
this.$http.get('/users/current')
.then(response => {
if (response.status === 200) {
this.currentUser = response.data.user
}
})
.catch((error) => {
console.error(error.response)
})
},
updateUserAdmin (id, isAdmin) {
this.$http.patch('/users/admin/'.concat(id), {
isAdmin: isAdmin
})
.then(response => {
if (response.status === 200) {
this.updateText = 'User updated'
}
})
.catch(function (error) {
console.error(error.response)
this.updateText = 'Error updating user'
})
},
updateUserUpload (id, canUpload) {
this.$http.patch('/users/upload/'.concat(id), {
canUpload: canUpload
})
.then(response => {
if (response.status === 200) {
this.updateText = 'User updated'
}
})
.catch(function (error) {
console.error(error.response)
this.updateText = 'Error updating user'
})
},
deleteUser (id) {
this.$http.delete('/users/'.concat(id))
.then(response => {
if (response.status === 200) {
this.getUsers()
}
})
.catch((error) => {
console.error(error.response)
})
}
}
}

51
src/components/appbar.js Normal file
View File

@@ -0,0 +1,51 @@
export default {
name: 'AppBar',
data: () => ({
drawyer: false,
isAdmin: false
}),
created () {
this.getCurrentUser()
},
methods: {
home () {
this.$router.push('/')
},
admin () {
this.$router.push('/admin')
},
logout () {
this.$http.get('/logout')
.then(response => {
if (response.status === 200) {
this.$router.push('/login')
}
})
.catch((error) => {
console.error(error.response)
})
},
getCurrentUser () {
this.$http.get('/users/current')
.then(response => {
if (response.status === 200) {
this.isAdmin = response.data.user.isAdmin
this.$vuetify.theme.dark = response.data.user.darkMode
}
})
.catch((error) => {
console.error(error.response)
this.isAdmin = false
})
},
toggleDarkMode () {
this.$http.patch('/users/dark-mode', {
darkMode: this.$vuetify.theme.dark
})
.catch((error) => {
console.error(error.response)
})
}
}
}

29
src/components/login.js Normal file
View File

@@ -0,0 +1,29 @@
export default {
data: () => ({
valid: false,
username: '',
password: '',
usernameRules: [
v => !!v || 'Username is required'
],
passwordRules: [
v => !!v || 'Password is required'
]
}),
methods: {
login () {
this.$http.post('/login/password', {
username: this.username,
password: this.password
})
.then(response => {
if (response.status === 200) {
this.$router.push('/')
}
})
.catch((error) => {
console.error(error.response)
})
}
}
}

View File

@@ -10,6 +10,8 @@ export default {
profileDialog: false,
profileName: '',
isProfileValid: false,
updateProfileSnackbar: false,
updateProfileText: '',
playDisabled: false,
isTimerEnabled: false,
hours: 0,
@@ -32,6 +34,7 @@ export default {
isTremoloEnabled: false,
tremoloFrequency: 0.5,
tremoloDepth: 0.5,
isReverbEnabled: false,
allSamples: [],
loadedSamples: [],
selectedSample: null,
@@ -39,8 +42,10 @@ export default {
addSampleDialog: false,
checkedSamples: [],
sampleName: '',
file: null,
isSampleUploadValid: false,
canUpload: false,
errorSnackbar: false,
errorSnackbarText: '',
rules: {
lt (n) {
return value => (!isNaN(parseInt(value, 10)) && value < n) || 'Must be less than ' + n
@@ -71,8 +76,9 @@ export default {
this.tremolo = new Tremolo()
this.lfo = new LFO()
this.players = new Players()
this.populateProfileItems()
this.populateProfileItems(0)
this.getSamples()
this.getCurrentUser()
},
methods: {
play () {
@@ -108,12 +114,14 @@ export default {
this.timeRemainingInterval = setInterval(() => this.startTimer(), 1000)
this.loadedSamples.forEach(s => {
this.players.player(s.id).loop = true
this.players.player(s.id).unsync().sync().start(0).stop(this.duration)
})
} else {
this.noise.sync().start(0)
this.loadedSamples.forEach(s => {
this.players.player(s.id).loop = true
this.players.player(s.id).unsync().sync().start(0)
})
}
@@ -181,7 +189,7 @@ export default {
this.noise.connect(this.filter)
}
},
populateProfileItems () {
populateProfileItems (profileId) {
this.$http.get('/profiles')
.then(response => {
if (response.status === 200) {
@@ -189,7 +197,7 @@ export default {
this.addDefaultProfile()
} else {
this.profileItems = response.data.profiles
this.selectedProfile = this.profileItems[0]
this.selectedProfile = this.profileItems[profileId]
}
}
})
@@ -201,7 +209,9 @@ export default {
this.$http.post('/profiles/default')
.then(response => {
if (response.status === 200) {
this.selectedProfile = { id: response.data.id, text: 'Default' }
const defaultProfile = { id: response.data.id, text: 'Default' }
this.profileItems = [defaultProfile]
this.selectedProfile = defaultProfile
}
})
.catch((error) => {
@@ -227,27 +237,45 @@ export default {
tremoloDepth: this.tremoloDepth,
samples: this.loadedSamples
}).then(response => {
const id = response.data.id
if (response.status === 200) {
this.profileDialog = false
this.populateProfileItems()
this.$http.get('/profiles')
.then(response => {
if (response.status === 200) {
this.profileItems = response.data.profiles
this.selectedProfile = { id: id, text: this.profileName }
}
})
.catch((error) => {
console.error(error.response)
})
console.log('repsonse.data.id: ', response.data.id)
this.populateProfileItems(response.data.id - 1)
}
})
.catch((error) => {
console.error(error.response)
})
},
updateProfile () {
this.$http.put('/profiles/'.concat(this.selectedProfile.id), {
isTimerEnabled: this.isTimerEnabled,
duration: this.duration,
volume: this.volume,
noiseColor: this.noiseColor,
isFilterEnabled: this.isFilterEnabled,
filterType: this.filterType,
filterCutoff: this.filterCutoff,
isLFOFilterCutoffEnabled: this.isLFOFilterCutoffEnabled,
lfoFilterCutoffFrequency: this.lfoFilterCutoffFrequency,
lfoFilterCutoffLow: this.lfoFilterCutoffRange[0],
lfoFilterCutoffHigh: this.lfoFilterCutoffRange[1],
isTremoloEnabled: this.isTremoloEnabled,
tremoloFrequency: this.tremoloFrequency,
tremoloDepth: this.tremoloDepth,
samples: this.loadedSamples
}).then(response => {
if (response.status === 200) {
this.updateProfileText = 'Profile saved'
}
})
.catch((error) => {
console.error(error.response)
this.updateProfileText = 'Error saving profile'
})
this.updateProfileSnackbar = true
},
loadProfile () {
this.$http.get('/profiles/'.concat(this.selectedProfile.id))
.then(response => {
@@ -280,7 +308,7 @@ export default {
this.$http.delete('/profiles/'.concat(this.selectedProfile.id))
.then(response => {
if (response.status === 200) {
this.populateProfileItems()
this.populateProfileItems(0)
}
})
.catch((error) => {
@@ -294,7 +322,7 @@ export default {
this.allSamples = response.data.samples
this.allSamples.forEach(s => {
if (!this.players.has(s.id)) {
this.players.add(s.id, '/samples/' + s.name).toDestination()
this.players.add(s.id, '/samples/' + s.user + '_' + s.name).toDestination()
}
})
}
@@ -320,6 +348,10 @@ export default {
}
})
.catch((error) => {
if (error.response.status === 409) {
this.errorSnackbarText = 'Upload Failed: Duplicate Sample Name'
this.errorSnackbar = true
}
console.error(error.response)
})
@@ -328,13 +360,29 @@ export default {
addSample () {
this.checkedSamples.forEach(i => {
const load = this.allSamples.find(e => e.id === i)
load.volume = -10
this.loadedSamples.push(load)
})
this.addSampleDialog = false
this.checkedSamples = []
},
updateSampleVolume (id, index) {
this.players.player(id).volume.value = this.loadedSamples[index].volume
},
removeSample (index) {
this.loadedSamples.splice(index, 1)
},
getCurrentUser () {
this.$http.get('/users/current')
.then(response => {
if (response.status === 200) {
this.canUpload = response.data.user.canUpload
}
})
.catch((error) => {
console.error(error.response)
})
}
}
}

View File

@@ -0,0 +1,31 @@
export default {
data: () => ({
valid: false,
name: '',
username: '',
password: '',
rules: {
required: v => !!v || 'Required'
}
}),
methods: {
register () {
this.$http.post('/users', {
name: this.name,
username: this.username,
password: this.password,
isAdmin: 1,
darkMode: 0,
canUpload: 1
})
.then(response => {
if (response.status === 200) {
this.$router.push('/login')
}
})
.catch((error) => {
console.error(error.response)
})
}
}
}

View File

@@ -1,6 +1,6 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import Home from '../views/HomeView.vue'
import instance from '../axios'
Vue.use(VueRouter)
@@ -13,24 +13,24 @@ const routes = [
},
{
path: '/login',
name: 'Signin',
name: 'Login',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/Signin.vue')
component: () => import(/* webpackChunkName: "about" */ '../views/LoginView.vue')
},
{
path: '/register',
name: 'Signup',
name: 'Register',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/Signup.vue')
component: () => import(/* webpackChunkName: "about" */ '../views/RegisterView.vue')
},
{
path: '/admin',
name: 'Admin',
component: () => import(/* webpackChunkName: "about" */ '../views/AdminPage.vue')
component: () => import(/* webpackChunkName: "about" */ '../views/AdminView.vue')
}
]

View File

@@ -1,5 +0,0 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>

View File

@@ -6,7 +6,7 @@
import Admin from '../components/Admin'
export default {
name: 'AdminPage',
name: 'AdminView',
components: {
Admin

View File

@@ -6,7 +6,7 @@
import Noise from '../components/Noise'
export default {
name: 'Home',
name: 'HomeView',
components: {
Noise

View File

@@ -6,7 +6,7 @@
import Login from '../components/Login'
export default {
name: 'Signin',
name: 'LoginView',
components: {
Login

View File

@@ -6,7 +6,7 @@
import Register from '../components/Register'
export default {
name: 'Signup',
name: 'RegisterView',
components: {
Register