Skip to content

Commit

Permalink
Rework UI, add auth system
Browse files Browse the repository at this point in the history
  • Loading branch information
oamaok committed Oct 16, 2021
1 parent e34b807 commit a9937fc
Show file tree
Hide file tree
Showing 63 changed files with 2,078 additions and 568 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
node_modules
dist
data/database.sqlite3
data/database.sqlite3
cert
27 changes: 27 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# TODO

## View offset stuffs:

- Single transform on a container
- Calculate bounding box for the cable SVG instead of using the viewport h/w. Should be easy just by iterating over socket positions. Maybe still resize so that the user cannot drag the cables outside the bounding box, so i.e. the SVG element's boundary will alway hug the viewport edge, but the minimum size will be defined by the bounding box.

## Modules

- Keyboard UI component (svg?)
- MIDI input
- Better oscillator, maybe multiple

## Knobs

- Exponential curves (for e.g. ADSR timings: presice short timings, impresice long ones)
- Unit types (seconds, Hz, volts, etc)

# Module selector

- Starred items
- Only accesible via hotkey?

# General UI/UX

- Hotkey configuration?
- Global volume control
13 changes: 8 additions & 5 deletions build.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const fs = require('fs/promises')
const path = require('path')
const esbuild = require('esbuild')
const chokidar = require('chokidar')
const CssModulesPlugin = require('./build/css-modules-plugin')

const recursiveCopy = async (srcDir, destDir) => {
const absoluteSrc = path.resolve(__dirname, srcDir)
Expand Down Expand Up @@ -42,14 +43,17 @@ const buildClient = async () => {
esbuild.build({
entryPoints: ['./client/src/index.tsx'],
bundle: true,
outfile: './dist/client/assets/main.js',
//outfile: './dist/client/assets/main.js',
outdir: './dist/client/assets',
incremental: true,
jsxFactory: 'h',
jsxFragment: 'Fragment',
minify: false,
minify: true,
define: {
// 'process.env.NODE_ENV': '"production"',
'process.env.NODE_ENV': '"production"',
},

plugins: [CssModulesPlugin()],
}),
recursiveCopy('./client/static', './dist/client'),
])
Expand Down Expand Up @@ -78,8 +82,7 @@ const buildWorklets = async () => {

await fs.writeFile(
'./client/src/generated/worklets.ts',
`
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
`// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
// THIS IS A GENERATED FILE, DO NOT EDIT MANUALLY
//
Expand Down
59 changes: 59 additions & 0 deletions build/css-modules-plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
const fs = require('fs/promises')
const { resolve } = require('path')
const postcss = require('postcss')
const postcssModules = require('postcss-modules')
const cssnano = require('cssnano')

const classNames = {}

let currentId = 0
const nextName = () => {
return '_' + (currentId++).toString(36)
}

const CssModulesPlugin = () => ({
name: 'CssModulesPlugin',
setup(build) {
const cssContent = {}

build.onLoad({ filter: /\.css$/ }, async ({ resolveDir, path }) => {
const filePath = resolve(resolveDir, path)
classNames[filePath] = classNames[filePath] ?? {}
let classNameMap = {}
const { css } = await postcss([
postcssModules({
getJSON(_, map) {
classNameMap = map
},
generateScopedName(name) {
if (!classNames[filePath][name]) {
classNames[filePath][name] = nextName()
}

const scopedName = classNames[filePath][name]
return scopedName
},
}),
cssnano({ preset: 'default' }),
]).process(await fs.readFile(filePath))

cssContent[filePath] = css

return {
contents: `export default ${JSON.stringify(classNameMap)}`,
}
})

build.onEnd(async () => {
const bundlePath = resolve(build.initialOptions.outdir, 'index.css')
await fs.writeFile(
bundlePath,
Object.keys(cssContent)
.map((key) => cssContent[key])
.join('\n')
)
})
},
})

module.exports = CssModulesPlugin
48 changes: 43 additions & 5 deletions client/src/api.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
import { Patch } from '../../common/types'
import * as auth from './auth'
import { Patch, UserLogin, UserRegistration } from '../../common/types'

type Method = 'GET' | 'POST'
type Options = {
body?: any
params?: any
params?: Record<string, any>
}

const request = (method: Method, endpoint: string, { body }: Options) => {
const token = localStorage.getItem('token')
const request = (
method: Method,
endpoint: string,
{ body, params }: Options
) => {
const token = auth.get()

return fetch(endpoint, {
const queryParams = params
? '?' +
Object.keys(params)
.map((key) => `${key}=${encodeURIComponent(params[key])}`)
.join('&')
: ''

return fetch(endpoint + queryParams, {
method,
body: body ? JSON.stringify(body) : undefined,
credentials: 'include',
Expand All @@ -35,3 +47,29 @@ export const getIdentity = () => {
export const saveNewPatch = (patch: Patch) => {
return post('/api/patch', { body: patch })
}

export const getCredentialsAvailability = ({
username,
email,
}: {
username: string
email: string
}): Promise<{ username: boolean; email: boolean }> => {
return get('/api/user/availability', { params: { username, email } })
}

export const register = (registration: UserRegistration) => {
return post('/api/user', {
body: registration,
})
}

export const login = (userLogin: UserLogin) => {
return post('/api/user/login', {
body: userLogin,
})
}

export const getLatestPatchVersion = (patchId: string) => {
return get(`/api/patch/${patchId}/latest`)
}
2 changes: 2 additions & 0 deletions client/src/audio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ export const initializeAudio = async () => {
for (const worklet of workletNames) {
const path = `/worklets/${worklet}.js`
try {
console.log(audioContext.audioWorklet)
await audioContext.audioWorklet.addModule(path)
} catch (err) {
console.error(err)
throw new Error(`Failed to load audio worklet: ${path}`)
}
}
Expand Down
16 changes: 16 additions & 0 deletions client/src/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { User } from '../../common/types'
import state from './state'

export const get = () => {
return localStorage.getItem('authorization')
}

export const set = ({ user, token }: { user: User; token: string }) => {
localStorage.setItem('authorization', token)
state.user = user
}

export const reset = () => {
localStorage.removeItem('authorization')
state.user = null
}
14 changes: 0 additions & 14 deletions client/src/components/App.tsx

This file was deleted.

15 changes: 0 additions & 15 deletions client/src/components/ModuleSelector.tsx

This file was deleted.

17 changes: 0 additions & 17 deletions client/src/components/Modules.tsx

This file was deleted.

87 changes: 87 additions & 0 deletions client/src/components/app/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { h, Fragment, useEffect } from 'kaiku'
import Header from '../header/Header'
import classNames from 'classnames/bind'
import styles from './app.css'
import UserBar from '../user-bar/user-bar'
import ModuleSelector from '../module-selector/ModuleSelector'
import Patch from '../patch/Patch'
import Hint from '../hint/Hint'
import { initializeAudio } from '../../audio'
import state, { patch } from '../../state'
import * as types from '../../../../common/types'
import * as api from '../../api'

const css = classNames.bind(styles)

const loadSaveState = async () => {
const rawSaveState = localStorage.getItem('savestate')
if (rawSaveState) {
loadPatch(JSON.parse(rawSaveState))
} else {
state.initialized = true
}
}

const initialize = async () => {
await initializeAudio()

switch (state.route.name) {
case 'index': {
await loadSaveState()
break
}

case 'patch': {
const patch = await api.getLatestPatchVersion(state.route.patchId)

if (!patch) {
history.replaceState({}, '', '/')
loadSaveState()
} else {
loadPatch(patch.patch)
}

break
}
}
}

const loadPatch = async (savedPatch: types.Patch) => {
const { currentId, modules, knobs, cables } = savedPatch
patch.knobs = knobs
patch.currentId = currentId
patch.modules = modules
state.initialized = true
await new Promise((resolve) => requestAnimationFrame(resolve))
patch.cables = cables
}

const App = () => {
useEffect(() => {
if (!state.initialized) return
if (state.route.name !== 'index') return

console.log('savestate')
localStorage.setItem('savestate', JSON.stringify(patch))
})

if (!state.initialized) {
return (
<button className="launch-button" onClick={initialize}>
Start
</button>
)
}

return (
<>
<Patch />
<Header />
<UserBar />
<ModuleSelector />
<Hint />
</>
)
}

export default App
Empty file.
Loading

0 comments on commit a9937fc

Please sign in to comment.