Frontend
This commit is contained in:
parent
59e243472b
commit
9f9b911ca0
|
@ -2,12 +2,7 @@ from flask import Flask, render_template, request, jsonify
|
|||
from .controller import Controller
|
||||
|
||||
|
||||
app = Flask(
|
||||
__name__,
|
||||
static_folder="./frontend/dist",
|
||||
static_url_path="/",
|
||||
template_folder="./frontend/dist",
|
||||
)
|
||||
app = Flask(__name__, static_folder="./client/build", static_url_path="/assets")
|
||||
controller = Controller()
|
||||
|
||||
|
||||
|
@ -46,13 +41,12 @@ def gain():
|
|||
return jsonify(controller.set_gain(amount))
|
||||
|
||||
|
||||
@app.route("/")
|
||||
@app.route("/", defaults={"path": ""})
|
||||
@app.route("/<path:path>")
|
||||
def index(path: str = ""):
|
||||
print(f"PATH: {path}")
|
||||
return render_template("index.html")
|
||||
def index(path: str):
|
||||
return render_template("index.html.j2")
|
||||
|
||||
|
||||
@app.errorhandler(404)
|
||||
def not_found(*args, **kwargs):
|
||||
return render_template("index.html")
|
||||
# @app.errorhandler(404)
|
||||
# def not_found(*args, **kwargs):
|
||||
# return render_template("index.html.j2")
|
||||
|
|
|
@ -2234,6 +2234,11 @@
|
|||
"integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==",
|
||||
"dev": true
|
||||
},
|
||||
"regexparam": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/regexparam/-/regexparam-1.3.0.tgz",
|
||||
"integrity": "sha512-6IQpFBv6e5vz1QAqI+V4k8P2e/3gRrqfCJ9FI+O1FLQTO+Uz6RXZEZOPmTJ6hlGj7gkERzY5BRCv09whKP96/g=="
|
||||
},
|
||||
"regexpu-core": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz",
|
||||
|
@ -2518,6 +2523,14 @@
|
|||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.16.7.tgz",
|
||||
"integrity": "sha512-egrva1UklB1n7KAv179IhDpQzMGAvubJUlOQ9PitmmZmAfrCUEgrQnx2vPxn2s+mGV3aYegXvJ/yQ35N2SfnYQ=="
|
||||
},
|
||||
"svelte-spa-router": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte-spa-router/-/svelte-spa-router-2.0.0.tgz",
|
||||
"integrity": "sha512-cIzRfHisJ87IzI9gyufkEXwaXT5hJrejMa3ryeClIzGLC2LzmKECfBhbitmYJ8gDfkto9tfZVXGvgQ9i7lmxXA==",
|
||||
"requires": {
|
||||
"regexparam": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"svgo": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz",
|
||||
|
|
|
@ -15,8 +15,16 @@
|
|||
"> 0.5%",
|
||||
"last 2 versions",
|
||||
"Firefox ESR",
|
||||
"not dead"
|
||||
"not dead",
|
||||
"iOS > 9.3"
|
||||
],
|
||||
"prettier": {
|
||||
"tabWidth": 2,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"bracketSpacing": false,
|
||||
"arrowParens": "always"
|
||||
},
|
||||
"devDependencies": {
|
||||
"postcss": "^7.0.24",
|
||||
"postcss-import": "^12.0.1",
|
||||
|
@ -29,6 +37,7 @@
|
|||
"tailwindcss": "^1.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"svelte": "^3.16.7"
|
||||
"svelte": "^3.16.7",
|
||||
"svelte-spa-router": "^2.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<script>
|
||||
import Router from 'svelte-spa-router'
|
||||
import routes from '../routes'
|
||||
import Header from './Header.svelte'
|
||||
</script>
|
||||
|
||||
<div class="container py-4">
|
||||
<Header />
|
||||
|
||||
<main>
|
||||
<Router {routes} />
|
||||
</main>
|
||||
</div>
|
|
@ -0,0 +1,60 @@
|
|||
<script>
|
||||
import {fade} from 'svelte/transition'
|
||||
import {push} from 'svelte-spa-router'
|
||||
import controller from '../controller'
|
||||
import {connected} from '../stores'
|
||||
|
||||
const defaults = {
|
||||
host: '0.0.0.0',
|
||||
port: 9800
|
||||
}
|
||||
|
||||
let host = defaults.host
|
||||
let port = defaults.port
|
||||
let submitButton
|
||||
let error = ''
|
||||
|
||||
function connect() {
|
||||
if (!host || !port) {
|
||||
error = 'You must supply a host and a port number to connect'
|
||||
return
|
||||
}
|
||||
|
||||
error = ''
|
||||
const buttonValue = submitButton.value
|
||||
submitButton.value = 'Connecting...'
|
||||
|
||||
controller
|
||||
.connect(host, port)
|
||||
.then((success) => {
|
||||
connected.set(success)
|
||||
push('/fonts')
|
||||
})
|
||||
.catch((err) => {
|
||||
error = 'Unable to connect. Try again or check the device.'
|
||||
})
|
||||
.finally(() => {
|
||||
submitButton.value = buttonValue
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if error}
|
||||
<p class="error" transition:fade>{error}</p>
|
||||
{/if}
|
||||
|
||||
<form on:submit|preventDefault={connect}>
|
||||
<p>
|
||||
<label for="host">Host</label>
|
||||
<input type="text" name="host" id="host" bind:value={host} />
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="port">Port</label>
|
||||
<input type="number" name="port" id="port" min="0" bind:value={port} />
|
||||
</p>
|
||||
|
||||
<p class="actions">
|
||||
<button type="submit" bind:this={submitButton}>Connect</button>
|
||||
</p>
|
||||
</form>
|
|
@ -0,0 +1,51 @@
|
|||
<script>
|
||||
import {onMount} from 'svelte'
|
||||
import {fade} from 'svelte/transition'
|
||||
import {link} from 'svelte-spa-router'
|
||||
import controller from '../controller'
|
||||
|
||||
let fonts = []
|
||||
let loading = false
|
||||
let error = ''
|
||||
|
||||
onMount(() => {
|
||||
refresh()
|
||||
})
|
||||
|
||||
function refresh() {
|
||||
error = ''
|
||||
loading = true
|
||||
|
||||
controller
|
||||
.getSoundfonts()
|
||||
.then((fetched) => {
|
||||
fonts = fetched
|
||||
})
|
||||
.catch((err) => {
|
||||
error = 'Could not fetch list of fonts'
|
||||
})
|
||||
.finally(() => (loading = false))
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if error}
|
||||
<p class="error" transition:fade>{error}</p>
|
||||
{/if}
|
||||
|
||||
<ul class="mb-4">
|
||||
{#each fonts as font}
|
||||
<li>
|
||||
<a href="/fonts/{font.id}" use:link>{font.name}</a>
|
||||
</li>
|
||||
{:else}
|
||||
{#if loading}
|
||||
<p class="text-gray-500">Loading...</p>
|
||||
{:else if !error}
|
||||
<p class="text-red-500">No fonts!</p>
|
||||
{/if}
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
{#if !loading}
|
||||
<button on:click|preventDefault={refresh}>Refresh</button>
|
||||
{/if}
|
|
@ -0,0 +1,75 @@
|
|||
<script>
|
||||
import {onMount} from 'svelte'
|
||||
import {fade} from 'svelte/transition'
|
||||
import controller from '../../controller'
|
||||
|
||||
export let params = {}
|
||||
|
||||
let instruments = []
|
||||
let loading = false
|
||||
let error = ''
|
||||
let selected = undefined
|
||||
|
||||
onMount(() => {
|
||||
refresh()
|
||||
})
|
||||
|
||||
function refresh() {
|
||||
error = ''
|
||||
loading = true
|
||||
|
||||
controller
|
||||
.getInstruments(params.id)
|
||||
.then((fetched) => {
|
||||
instruments = fetched
|
||||
})
|
||||
.catch((err) => {
|
||||
error = 'Could not fetch list of instruments'
|
||||
})
|
||||
.finally(() => (loading = false))
|
||||
}
|
||||
|
||||
function select(instrument) {
|
||||
error = ''
|
||||
|
||||
controller
|
||||
.select({
|
||||
channel: 0,
|
||||
instrument: params.id,
|
||||
bank: instrument.bank,
|
||||
program: instrument.program
|
||||
})
|
||||
.then(() => {
|
||||
selected = instrument
|
||||
})
|
||||
.catch((err) => (error = 'Could not select instrument'))
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if error}
|
||||
<p class="error" transition:fade>{error}</p>
|
||||
{/if}
|
||||
|
||||
<ul class="mb-4">
|
||||
{#each instruments as instrument}
|
||||
<li
|
||||
class="bg-gray-200 dark:bg-gray-800 p-2 border-b border-gray-400
|
||||
dark:border-gray-600 cursor-pointer flex items-center"
|
||||
on:click|preventDefault={() => select(instrument)}>
|
||||
<span class="flex-auto">{instrument.name}</span>
|
||||
<span>
|
||||
{#if selected === instrument}✅{/if}
|
||||
</span>
|
||||
</li>
|
||||
{:else}
|
||||
{#if loading}
|
||||
<p class="text-gray-500">Loading...</p>
|
||||
{:else if !error}
|
||||
<p>No instruments!</p>
|
||||
{/if}
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
{#if !loading}
|
||||
<button on:click|preventDefault={refresh}>Refresh</button>
|
||||
{/if}
|
|
@ -0,0 +1,22 @@
|
|||
<script>
|
||||
import {link} from 'svelte-spa-router'
|
||||
import {connected} from '../stores'
|
||||
</script>
|
||||
|
||||
<header class="flex items-center mb-4">
|
||||
<nav class="flex-auto horizontal">
|
||||
<ul>
|
||||
{#if $connected}
|
||||
<li>
|
||||
<a href="/fonts" use:link>Fonts</a>
|
||||
</li>
|
||||
{:else}
|
||||
<li>
|
||||
<a href="/connect" use:link>Connect</a>
|
||||
</li>
|
||||
{/if}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div class="flex-none">{$connected ? '✅' : '❌'}</div>
|
||||
</header>
|
|
@ -0,0 +1,61 @@
|
|||
class Controller {
|
||||
constructor() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
async connect(host = '0.0.0.0', port = 9800) {
|
||||
const url = this.apiURL('/api/connect', {
|
||||
host,
|
||||
port
|
||||
})
|
||||
|
||||
const response = await fetch(url)
|
||||
const data = await response.json()
|
||||
|
||||
return data.success
|
||||
}
|
||||
|
||||
async getSoundfonts() {
|
||||
const url = this.apiURL('/api/soundfonts')
|
||||
|
||||
const response = await fetch(url)
|
||||
|
||||
return response.json()
|
||||
}
|
||||
|
||||
async getInstruments(font) {
|
||||
const url = this.apiURL('/api/instruments', {
|
||||
font
|
||||
})
|
||||
|
||||
const response = await fetch(url)
|
||||
|
||||
return response.json()
|
||||
}
|
||||
|
||||
async select({channel, instrument, bank, program}) {
|
||||
const url = this.apiURL('/api/select', {channel, instrument, bank, program})
|
||||
const response = await fetch(url)
|
||||
const data = response.json()
|
||||
|
||||
return data.success
|
||||
}
|
||||
|
||||
// Private
|
||||
|
||||
apiURL(path, params = {}) {
|
||||
const url = new URL(document.location)
|
||||
url.pathname = path
|
||||
url.hash = ''
|
||||
|
||||
for (let [key, value] of Object.entries(params)) {
|
||||
url.searchParams.append(key, value)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
const controller = new Controller()
|
||||
|
||||
export default controller
|
|
@ -0,0 +1,73 @@
|
|||
form {
|
||||
& > p {
|
||||
@apply mb-4;
|
||||
|
||||
@screen md {
|
||||
@apply flex items-center;
|
||||
}
|
||||
}
|
||||
|
||||
& label {
|
||||
@apply font-semibold block w-full mb-2;
|
||||
|
||||
@screen md {
|
||||
@apply w-1/6 inline-block mr-4 mb-0;
|
||||
}
|
||||
}
|
||||
|
||||
& input:not([type="submit"]), & textarea, & select {
|
||||
@apply w-full;
|
||||
|
||||
@screen md {
|
||||
@apply flex-auto;
|
||||
}
|
||||
}
|
||||
|
||||
& > .actions {
|
||||
@screen md {
|
||||
@apply w-5/6 ml-auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input, textarea {
|
||||
@apply bg-white text-gray-900 px-3 py-2 rounded border border-gray-300;
|
||||
|
||||
&:focus {
|
||||
@apply bg-gray-100;
|
||||
}
|
||||
|
||||
@screen dark {
|
||||
@apply bg-black text-gray-100 border border-gray-600;
|
||||
|
||||
&:focus {
|
||||
@apply bg-gray-900;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input[type="submit"], button, .button {
|
||||
@apply bg-gray-400 text-gray-900 px-4 py-2 rounded;
|
||||
}
|
||||
|
||||
.error {
|
||||
@apply bg-red-100 text-red-900 p-2 mb-4 rounded;
|
||||
|
||||
@screen dark {
|
||||
@apply bg-red-700 text-red-100;
|
||||
}
|
||||
}
|
||||
|
||||
nav.horizontal {
|
||||
& li {
|
||||
@apply inline-block mr-4;
|
||||
|
||||
&:last-child {
|
||||
@apply mr-0;
|
||||
}
|
||||
}
|
||||
|
||||
& :any-link {
|
||||
@apply underline;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
@import "tailwindcss/base";
|
||||
@import "tailwindcss/components";
|
||||
|
||||
@import "./custom.css";
|
||||
|
||||
@import "tailwindcss/utilities";
|
|
@ -0,0 +1,10 @@
|
|||
import './css/main.css'
|
||||
import App from './components/App.svelte'
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.querySelector('#loading').remove()
|
||||
|
||||
const app = new App({
|
||||
target: document.querySelector('#app')
|
||||
})
|
||||
})
|
|
@ -0,0 +1,9 @@
|
|||
import Connect from './components/Connect.svelte'
|
||||
import Fonts from './components/Fonts.svelte'
|
||||
import Font from './components/Fonts/Font.svelte'
|
||||
|
||||
export default {
|
||||
'/connect': Connect,
|
||||
'/fonts': Fonts,
|
||||
'/fonts/:id': Font
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import {writable} from 'svelte/store'
|
||||
|
||||
export const connected = writable(false)
|
||||
export const fonts = writable([])
|
||||
export const instruments = writable([])
|
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>FluidControl</title>
|
||||
<link rel="stylesheet" href="{{ url_for("static", filename="main.css") }}">
|
||||
<script src="{{ url_for("static", filename="main.js") }}" type="text/javascript"></script>
|
||||
</head>
|
||||
<body class="bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100">
|
||||
<div id="app">
|
||||
<div id="loading" class="p-4">Loading…</div>
|
||||
</div>
|
||||
<noscript>You must have JavaScript enabled for FluidControl to work.</noscript>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue