Implemeeeeeent
This commit is contained in:
120
boilerbloat/src/main/backend.ts
Normal file
120
boilerbloat/src/main/backend.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
|
||||
import { ipcMain } from 'electron';
|
||||
import { blackout } from '../patterns/blackout';
|
||||
import { Pattern, PatternOutput, Time } from '../patterns/proto';
|
||||
import { TestPattern } from '../patterns/test';
|
||||
import rust, { BeatTrackerHandle, MovingHeadState, OutputHandle } from 'rust_native_module';
|
||||
|
||||
type AppState = {
|
||||
patterns: { [key: string]: PatternOutput },
|
||||
selectedPattern: string | null,
|
||||
};
|
||||
|
||||
class Backend {
|
||||
|
||||
beatTracker: BeatTrackerHandle;
|
||||
dmxOutput: OutputHandle;
|
||||
|
||||
patterns: Map<string, Pattern>;
|
||||
patternOutputs: Map<string, PatternOutput>;
|
||||
selectedPattern: string;
|
||||
|
||||
constructor() {
|
||||
|
||||
// beat tracking
|
||||
|
||||
let beatTracker = rust.getBeatTracker();
|
||||
|
||||
if (beatTracker.type !== 'success') {
|
||||
throw new Error("could not initialize beat tracking");
|
||||
}
|
||||
|
||||
this.beatTracker = beatTracker.value;
|
||||
ipcMain.on('beat-tracking', async (_, arg) => {
|
||||
if (arg === 'tap') {
|
||||
this.beatTracker.tap();
|
||||
}
|
||||
});
|
||||
|
||||
// output
|
||||
|
||||
let dmxOutput = rust.openOutput("/dev/ttyUSB0");
|
||||
|
||||
if (dmxOutput.type !== 'success') {
|
||||
throw new Error("could not open DMX output");
|
||||
}
|
||||
|
||||
this.dmxOutput = dmxOutput.value;
|
||||
|
||||
// patterns
|
||||
|
||||
this.patterns = new Map();
|
||||
this.patterns.set("test", new TestPattern());
|
||||
|
||||
this.patternOutputs = new Map();
|
||||
this.selectedPattern = "test";
|
||||
|
||||
let time: Time = {
|
||||
absolute: 0,
|
||||
beat_relative: null
|
||||
}
|
||||
|
||||
for (let [patternId, pattern] of this.patterns.entries()) {
|
||||
this.patternOutputs.set(patternId, pattern.render(time));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
getState(): AppState {
|
||||
let result: AppState = {
|
||||
patterns: {},
|
||||
selectedPattern: this.selectedPattern,
|
||||
}
|
||||
|
||||
for (let [patternId, patternOutput] of this.patternOutputs) {
|
||||
result.patterns[patternId] = patternOutput;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
update() {
|
||||
let date = new Date();
|
||||
let time: Time = {
|
||||
absolute: date.getTime() / 1000,
|
||||
beat_relative: null,
|
||||
}
|
||||
|
||||
// render all patterns
|
||||
|
||||
if (!this.patterns) {
|
||||
console.log("big oof?");
|
||||
throw new Error("???");
|
||||
}
|
||||
|
||||
for (let [patternId, pattern] of this.patterns.entries()) {
|
||||
this.patternOutputs.set(patternId, pattern.render(time));
|
||||
}
|
||||
|
||||
// write selected pattern
|
||||
|
||||
let output: Array<MovingHeadState> = blackout;
|
||||
|
||||
if (this.selectedPattern !== null) {
|
||||
let selectedPatternOutput = this.patternOutputs.get(this.selectedPattern)
|
||||
|
||||
if (selectedPatternOutput) {
|
||||
output = selectedPatternOutput;
|
||||
}
|
||||
}
|
||||
|
||||
this.dmxOutput.set(output);
|
||||
}
|
||||
|
||||
close() {
|
||||
this.dmxOutput.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Backend;
|
||||
@@ -11,28 +11,20 @@
|
||||
import 'core-js/stable';
|
||||
import 'regenerator-runtime/runtime';
|
||||
import path from 'path';
|
||||
import { app, BrowserWindow, shell, ipcMain } from 'electron';
|
||||
import { app, BrowserWindow, ipcMain, shell } from 'electron';
|
||||
import { resolveHtmlPath } from './util';
|
||||
import Backend from './backend';
|
||||
|
||||
import rust from 'rust_native_module';
|
||||
let backend = new Backend();
|
||||
|
||||
let mainLoop = setInterval(() => { backend.update(); }, 20);
|
||||
|
||||
ipcMain.handle('poll', () => {
|
||||
return backend.getState();
|
||||
});
|
||||
|
||||
let mainWindow: BrowserWindow | null = null;
|
||||
|
||||
let beat_tracker = rust.getBeatTracker();
|
||||
|
||||
// TODO @thamma why does this not infer that beat_tracker has tap?
|
||||
if (beat_tracker.type === 'success') {
|
||||
|
||||
console.log('Beat Tracker started.');
|
||||
|
||||
ipcMain.on('beat-tracking', async (_, arg) => {
|
||||
if (arg === 'tap') {
|
||||
// see here
|
||||
(beat_tracker as any).tap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const createWindow = async () => {
|
||||
const RESOURCES_PATH = app.isPackaged
|
||||
? path.join(process.resourcesPath, 'assets')
|
||||
@@ -99,5 +91,9 @@ app
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (mainWindow === null) createWindow();
|
||||
});
|
||||
app.on('quit', () => {
|
||||
clearInterval(mainLoop);
|
||||
backend.close();
|
||||
})
|
||||
})
|
||||
.catch(console.log);
|
||||
|
||||
@@ -1,290 +0,0 @@
|
||||
import {
|
||||
app,
|
||||
Menu,
|
||||
shell,
|
||||
BrowserWindow,
|
||||
MenuItemConstructorOptions,
|
||||
} from 'electron';
|
||||
|
||||
interface DarwinMenuItemConstructorOptions extends MenuItemConstructorOptions {
|
||||
selector?: string;
|
||||
submenu?: DarwinMenuItemConstructorOptions[] | Menu;
|
||||
}
|
||||
|
||||
export default class MenuBuilder {
|
||||
mainWindow: BrowserWindow;
|
||||
|
||||
constructor(mainWindow: BrowserWindow) {
|
||||
this.mainWindow = mainWindow;
|
||||
}
|
||||
|
||||
buildMenu(): Menu {
|
||||
if (
|
||||
process.env.NODE_ENV === 'development' ||
|
||||
process.env.DEBUG_PROD === 'true'
|
||||
) {
|
||||
this.setupDevelopmentEnvironment();
|
||||
}
|
||||
|
||||
const template =
|
||||
process.platform === 'darwin'
|
||||
? this.buildDarwinTemplate()
|
||||
: this.buildDefaultTemplate();
|
||||
|
||||
const menu = Menu.buildFromTemplate(template);
|
||||
Menu.setApplicationMenu(menu);
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
setupDevelopmentEnvironment(): void {
|
||||
this.mainWindow.webContents.on('context-menu', (_, props) => {
|
||||
const { x, y } = props;
|
||||
|
||||
Menu.buildFromTemplate([
|
||||
{
|
||||
label: 'Inspect element',
|
||||
click: () => {
|
||||
this.mainWindow.webContents.inspectElement(x, y);
|
||||
},
|
||||
},
|
||||
]).popup({ window: this.mainWindow });
|
||||
});
|
||||
}
|
||||
|
||||
buildDarwinTemplate(): MenuItemConstructorOptions[] {
|
||||
const subMenuAbout: DarwinMenuItemConstructorOptions = {
|
||||
label: 'Electron',
|
||||
submenu: [
|
||||
{
|
||||
label: 'About ElectronReact',
|
||||
selector: 'orderFrontStandardAboutPanel:',
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{ label: 'Services', submenu: [] },
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Hide ElectronReact',
|
||||
accelerator: 'Command+H',
|
||||
selector: 'hide:',
|
||||
},
|
||||
{
|
||||
label: 'Hide Others',
|
||||
accelerator: 'Command+Shift+H',
|
||||
selector: 'hideOtherApplications:',
|
||||
},
|
||||
{ label: 'Show All', selector: 'unhideAllApplications:' },
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Quit',
|
||||
accelerator: 'Command+Q',
|
||||
click: () => {
|
||||
app.quit();
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const subMenuEdit: DarwinMenuItemConstructorOptions = {
|
||||
label: 'Edit',
|
||||
submenu: [
|
||||
{ label: 'Undo', accelerator: 'Command+Z', selector: 'undo:' },
|
||||
{ label: 'Redo', accelerator: 'Shift+Command+Z', selector: 'redo:' },
|
||||
{ type: 'separator' },
|
||||
{ label: 'Cut', accelerator: 'Command+X', selector: 'cut:' },
|
||||
{ label: 'Copy', accelerator: 'Command+C', selector: 'copy:' },
|
||||
{ label: 'Paste', accelerator: 'Command+V', selector: 'paste:' },
|
||||
{
|
||||
label: 'Select All',
|
||||
accelerator: 'Command+A',
|
||||
selector: 'selectAll:',
|
||||
},
|
||||
],
|
||||
};
|
||||
const subMenuViewDev: MenuItemConstructorOptions = {
|
||||
label: 'View',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Reload',
|
||||
accelerator: 'Command+R',
|
||||
click: () => {
|
||||
this.mainWindow.webContents.reload();
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Toggle Full Screen',
|
||||
accelerator: 'Ctrl+Command+F',
|
||||
click: () => {
|
||||
this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen());
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Toggle Developer Tools',
|
||||
accelerator: 'Alt+Command+I',
|
||||
click: () => {
|
||||
this.mainWindow.webContents.toggleDevTools();
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const subMenuViewProd: MenuItemConstructorOptions = {
|
||||
label: 'View',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Toggle Full Screen',
|
||||
accelerator: 'Ctrl+Command+F',
|
||||
click: () => {
|
||||
this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen());
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const subMenuWindow: DarwinMenuItemConstructorOptions = {
|
||||
label: 'Window',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Minimize',
|
||||
accelerator: 'Command+M',
|
||||
selector: 'performMiniaturize:',
|
||||
},
|
||||
{ label: 'Close', accelerator: 'Command+W', selector: 'performClose:' },
|
||||
{ type: 'separator' },
|
||||
{ label: 'Bring All to Front', selector: 'arrangeInFront:' },
|
||||
],
|
||||
};
|
||||
const subMenuHelp: MenuItemConstructorOptions = {
|
||||
label: 'Help',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Learn More',
|
||||
click() {
|
||||
shell.openExternal('https://electronjs.org');
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Documentation',
|
||||
click() {
|
||||
shell.openExternal(
|
||||
'https://github.com/electron/electron/tree/main/docs#readme'
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Community Discussions',
|
||||
click() {
|
||||
shell.openExternal('https://www.electronjs.org/community');
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Search Issues',
|
||||
click() {
|
||||
shell.openExternal('https://github.com/electron/electron/issues');
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const subMenuView =
|
||||
process.env.NODE_ENV === 'development' ||
|
||||
process.env.DEBUG_PROD === 'true'
|
||||
? subMenuViewDev
|
||||
: subMenuViewProd;
|
||||
|
||||
return [subMenuAbout, subMenuEdit, subMenuView, subMenuWindow, subMenuHelp];
|
||||
}
|
||||
|
||||
buildDefaultTemplate() {
|
||||
const templateDefault = [
|
||||
{
|
||||
label: '&File',
|
||||
submenu: [
|
||||
{
|
||||
label: '&Open',
|
||||
accelerator: 'Ctrl+O',
|
||||
},
|
||||
{
|
||||
label: '&Close',
|
||||
accelerator: 'Ctrl+W',
|
||||
click: () => {
|
||||
this.mainWindow.close();
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '&View',
|
||||
submenu:
|
||||
process.env.NODE_ENV === 'development' ||
|
||||
process.env.DEBUG_PROD === 'true'
|
||||
? [
|
||||
{
|
||||
label: '&Reload',
|
||||
accelerator: 'Ctrl+R',
|
||||
click: () => {
|
||||
this.mainWindow.webContents.reload();
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Toggle &Full Screen',
|
||||
accelerator: 'F11',
|
||||
click: () => {
|
||||
this.mainWindow.setFullScreen(
|
||||
!this.mainWindow.isFullScreen()
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Toggle &Developer Tools',
|
||||
accelerator: 'Alt+Ctrl+I',
|
||||
click: () => {
|
||||
this.mainWindow.webContents.toggleDevTools();
|
||||
},
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
label: 'Toggle &Full Screen',
|
||||
accelerator: 'F11',
|
||||
click: () => {
|
||||
this.mainWindow.setFullScreen(
|
||||
!this.mainWindow.isFullScreen()
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Help',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Learn More',
|
||||
click() {
|
||||
shell.openExternal('https://electronjs.org');
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Documentation',
|
||||
click() {
|
||||
shell.openExternal(
|
||||
'https://github.com/electron/electron/tree/main/docs#readme'
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Community Discussions',
|
||||
click() {
|
||||
shell.openExternal('https://www.electronjs.org/community');
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Search Issues',
|
||||
click() {
|
||||
shell.openExternal('https://github.com/electron/electron/issues');
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return templateDefault;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,5 @@
|
||||
const { contextBridge, ipcRenderer } = require('electron');
|
||||
const rust = require('rust_native_module');
|
||||
|
||||
contextBridge.exposeInMainWorld('electron', {
|
||||
ipcRenderer: {
|
||||
send(channel, data) {
|
||||
const validChannels = ['beat-tracking'];
|
||||
if (validChannels.includes(channel)) {
|
||||
ipcRenderer.send(channel, data);
|
||||
}
|
||||
},
|
||||
on(channel, func) {
|
||||
const validChannels = ['tick', 'beat-tracking'];
|
||||
if (validChannels.includes(channel)) {
|
||||
// Deliberately strip event as it includes `sender`
|
||||
ipcRenderer.on(channel, (event, ...args) => func(...args));
|
||||
}
|
||||
},
|
||||
},
|
||||
rustding: rust
|
||||
ipcRenderer: ipcRenderer,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user