Skip to content

Commit

Permalink
Boom!
Browse files Browse the repository at this point in the history
  • Loading branch information
oamaok committed Oct 13, 2021
0 parents commit a7b87dc
Show file tree
Hide file tree
Showing 51 changed files with 4,453 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
dist
data/database.sqlite3
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dist
client/generated
server/migrations/
4 changes: 4 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"semi": false,
"singleQuote": true
}
119 changes: 119 additions & 0 deletions build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
const fs = require('fs/promises')
const path = require('path')
const esbuild = require('esbuild')
const chokidar = require('chokidar')

const recursiveCopy = async (srcDir, destDir) => {
const absoluteSrc = path.resolve(__dirname, srcDir)
const absoluteDest = path.resolve(__dirname, destDir)
let entryStack = await fs.readdir(srcDir)
let entry

while ((entry = entryStack.pop())) {
const entryPath = path.resolve(absoluteSrc, entry)
const stats = await fs.stat(entryPath)

const destPath = path.join(absoluteDest, entry)
if (stats.isDirectory()) {
try {
const destStats = await fs.stat(destPath)
if (!destStats.isDirectory()) {
throw new Error(
`Destination path exists and is not a directory: ${destPath}`
)
}
} catch (err) {
await fs.mkdir(destPath)
}

entryStack.push(
...(await fs.readdir(entryPath)).map((e) => path.join(entry, e))
)
} else {
await fs.copyFile(entryPath, destPath)
}
}
}

const buildClient = async () => {
console.time('Build client')

await Promise.all([
esbuild.build({
entryPoints: ['./client/src/index.tsx'],
bundle: true,
outfile: './dist/client/assets/main.js',
incremental: true,
jsxFactory: 'h',
jsxFragment: 'Fragment',
minify: false,
define: {
// 'process.env.NODE_ENV': '"production"',
},
}),
recursiveCopy('./client/static', './dist/client'),
])

console.timeEnd('Build client')
}

const buildWorklets = async () => {
console.time('Build worklets')

const worklets = (await fs.readdir('./client/worklets'))
.filter((worklet) => worklet.endsWith('.ts'))
.map((worklet) => worklet.split('.')[0])

await Promise.all(
worklets.map((worklet) =>
esbuild.build({
entryPoints: [`./client/worklets/${worklet}.ts`],
bundle: true,
outfile: `./dist/client/worklets/${worklet}.js`,
incremental: true,
minify: false,
})
)
)

await fs.writeFile(
'./client/src/generated/worklets.ts',
`
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
// THIS IS A GENERATED FILE, DO NOT EDIT MANUALLY
//
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
${worklets
.map((worklet) => `import ${worklet} from '../../worklets/${worklet}'`)
.join('\n')}
export const workletNames = ${JSON.stringify(worklets)} as const
export type Worklets = {
${worklets.map((worklet) => ` ${worklet}: typeof ${worklet}`).join('\n')}
}
`
)
console.timeEnd('Build worklets')
}

const buildServer = () => {}

;(async () => {
await buildWorklets()
await buildClient()
await buildServer()

chokidar
.watch('./worklets/*', {
persistent: true,
ignoreInitial: true,
})
.on('all', buildWorklets)

chokidar
.watch('./client/**/*', {
persistent: true,
ignoreInitial: true,
})
.on('all', buildClient)
})()
31 changes: 31 additions & 0 deletions client/src/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
type Method = 'GET' | 'POST'
type Options = {
body?: any
params?: any
}

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

return fetch(endpoint, {
method,
body: body ? JSON.stringify(body) : undefined,
credentials: 'include',
headers: {
Accept: 'application/json',
Authorization: token ? `Bearer ${token}` : '',
},
}).then((res) => res.json())
}

const get = (endpoint: string, options?: Options) => {
return request('GET', endpoint, { ...options })
}

const post = (endpoint: string, options?: Options) => {
return request('POST', endpoint, { ...options })
}

export const getIdentity = () => {
return get('/api/identity')
}
27 changes: 27 additions & 0 deletions client/src/audio.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { workletNames, WorkletNode } from './worklets'

let audioContext: AudioContext | null = null

export const initializeAudio = async () => {
audioContext = new AudioContext()
for (const worklet of workletNames) {
const path = `/worklets/${worklet}.js`
try {
await audioContext.audioWorklet.addModule(path)
} catch (err) {
throw new Error(`Failed to load audio worklet: ${path}`)
}
}
}

export const getAudioContext = () => {
if (!audioContext) {
throw new Error('AudioContext not initialized')
}

return audioContext
}

export const controlVoltageToFrequency = (voltage: number) => {
return 13.75 * 2 ** voltage
}
33 changes: 33 additions & 0 deletions client/src/components/ActiveCable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { h, useEffect } from 'kaiku'
import state, {
getCableConnectionCandidate,
getSocketPosition,
releaseActiveCable,
} from '../state'
import CablePath from './CablePath'

const ActiveCable = () => {
useEffect(() => {
if (state.activeCable) {
document.addEventListener('mouseup', releaseActiveCable)

return () => {
document.removeEventListener('mouseup', releaseActiveCable)
}
}
})

if (!state.activeCable) return null

const candidateSocket = getCableConnectionCandidate()

const a = getSocketPosition(state.activeCable.from.socket)
const b = candidateSocket ? getSocketPosition(candidateSocket) : state.cursor

const from = state.activeCable.from.socket.type === 'output' ? a : b
const to = state.activeCable.from.socket.type === 'output' ? b : a

return <CablePath from={from} to={to} />
}

export default ActiveCable
54 changes: 54 additions & 0 deletions client/src/components/Cable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { h, useEffect } from 'kaiku'
import { getRegisteredSocket, getSockets } from '../sockets'
import { getSocketPosition } from '../state'
import { ConnectedSocket } from '../types'
import CablePath from './CablePath'

type Props = {
from: ConnectedSocket
to: ConnectedSocket
}

const Cable = ({ from, to }: Props) => {
useEffect(() => {
const outputSocket = getRegisteredSocket(from.moduleId, from.name)
const inputSocket = getRegisteredSocket(to.moduleId, to.name)

if (outputSocket.type === 'input') {
throw new Error('invalid outputSocket type')
}

if (inputSocket.type === 'output') {
throw new Error('invalid inputSocket type')
}

if (inputSocket.node instanceof AudioNode) {
outputSocket.node.connect(inputSocket.node)
} else {
outputSocket.node.connect(inputSocket.node)
}

return () => {
const outputSocket = getRegisteredSocket(from.moduleId, from.name)
const inputSocket = getRegisteredSocket(to.moduleId, to.name)

if (outputSocket.type === 'input') {
throw new Error('invalid outputSocket type')
}

if (inputSocket.type === 'output') {
throw new Error('invalid inputSocket type')
}

if (inputSocket.node instanceof AudioNode) {
outputSocket.node.disconnect(inputSocket.node)
} else {
outputSocket.node.disconnect(inputSocket.node)
}
}
})

return <CablePath from={getSocketPosition(from)} to={getSocketPosition(to)} />
}

export default Cable
28 changes: 28 additions & 0 deletions client/src/components/CablePath.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { h } from 'kaiku'
import { Vec2 } from '../types'

type Props = {
from: Vec2
to: Vec2
}

const CablePath = ({ from, to }: Props) => {
const controlPointOffset = Math.floor(Math.sqrt(Math.abs(from.y - to.y)) * 10)

return (
<path
d={`
M ${from.x} ${from.y}
C ${from.x + controlPointOffset} ${from.y}
${to.x - controlPointOffset} ${to.y}
${to.x} ${to.y}
`}
stroke-width="3"
stroke-linecap="round"
stroke="rgba(255,255,255,0.5)"
fill="transparent"
/>
)
}

export default CablePath
18 changes: 18 additions & 0 deletions client/src/components/Cables.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { h } from 'kaiku'
import state from '../state'

import ActiveCable from './ActiveCable'
import Cable from './Cable'

const Cables = () => (
<div className="cables">
<svg viewBox={`0 0 ${state.viewport.width} ${state.viewport.height}`}>
{state.cables.map(({ id, from, to }) => (
<Cable key={id} from={from.socket} to={to.socket} />
))}
<ActiveCable />
</svg>
</div>
)

export default Cables
Loading

0 comments on commit a7b87dc

Please sign in to comment.