This commit is contained in:
Evan Walsh 2020-01-02 14:42:02 -05:00
parent 59e243472b
commit 9f9b911ca0
15 changed files with 433 additions and 15 deletions

View File

@ -2,12 +2,7 @@ from flask import Flask, render_template, request, jsonify
from .controller import Controller from .controller import Controller
app = Flask( app = Flask(__name__, static_folder="./client/build", static_url_path="/assets")
__name__,
static_folder="./frontend/dist",
static_url_path="/",
template_folder="./frontend/dist",
)
controller = Controller() controller = Controller()
@ -46,13 +41,12 @@ def gain():
return jsonify(controller.set_gain(amount)) return jsonify(controller.set_gain(amount))
@app.route("/") @app.route("/", defaults={"path": ""})
@app.route("/<path:path>") @app.route("/<path:path>")
def index(path: str = ""): def index(path: str):
print(f"PATH: {path}") return render_template("index.html.j2")
return render_template("index.html")
@app.errorhandler(404) # @app.errorhandler(404)
def not_found(*args, **kwargs): # def not_found(*args, **kwargs):
return render_template("index.html") # return render_template("index.html.j2")

View File

@ -2234,6 +2234,11 @@
"integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==",
"dev": true "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": { "regexpu-core": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", "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", "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.16.7.tgz",
"integrity": "sha512-egrva1UklB1n7KAv179IhDpQzMGAvubJUlOQ9PitmmZmAfrCUEgrQnx2vPxn2s+mGV3aYegXvJ/yQ35N2SfnYQ==" "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": { "svgo": {
"version": "1.3.2", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz",

View File

@ -15,8 +15,16 @@
"> 0.5%", "> 0.5%",
"last 2 versions", "last 2 versions",
"Firefox ESR", "Firefox ESR",
"not dead" "not dead",
"iOS > 9.3"
], ],
"prettier": {
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"bracketSpacing": false,
"arrowParens": "always"
},
"devDependencies": { "devDependencies": {
"postcss": "^7.0.24", "postcss": "^7.0.24",
"postcss-import": "^12.0.1", "postcss-import": "^12.0.1",
@ -29,6 +37,7 @@
"tailwindcss": "^1.1.4" "tailwindcss": "^1.1.4"
}, },
"dependencies": { "dependencies": {
"svelte": "^3.16.7" "svelte": "^3.16.7",
"svelte-spa-router": "^2.0.0"
} }
} }

View File

@ -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>

View File

@ -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>

View File

@ -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}

View File

@ -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}

View File

@ -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>

View File

@ -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

View File

@ -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;
}
}

View File

@ -0,0 +1,6 @@
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "./custom.css";
@import "tailwindcss/utilities";

View File

@ -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')
})
})

View File

@ -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
}

View File

@ -0,0 +1,5 @@
import {writable} from 'svelte/store'
export const connected = writable(false)
export const fonts = writable([])
export const instruments = writable([])

View File

@ -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>